@@ -1209,6 +1209,205 @@ def test_codex_skill_registration_resolves_script_placeholders(self, project_dir
12091209 assert '.specify/scripts/bash/setup-plan.sh --json "$ARGUMENTS"' in content
12101210 assert ".specify/scripts/bash/update-agent-context.sh codex" in content
12111211
1212+ def test_skill_registration_rewrites_extension_relative_paths (self , project_dir , temp_dir ):
1213+ """Extension subdirectory paths in command bodies should be rewritten to
1214+ .specify/extensions/<id>/... in generated SKILL.md files."""
1215+ import yaml
1216+
1217+ ext_dir = temp_dir / "ext-multidir"
1218+ ext_dir .mkdir ()
1219+ (ext_dir / "commands" ).mkdir ()
1220+ (ext_dir / "agents" ).mkdir ()
1221+ (ext_dir / "templates" ).mkdir ()
1222+ (ext_dir / "scripts" ).mkdir ()
1223+ (ext_dir / "knowledge-base" ).mkdir ()
1224+
1225+ manifest_data = {
1226+ "schema_version" : "1.0" ,
1227+ "extension" : {
1228+ "id" : "ext-multidir" ,
1229+ "name" : "Multi-Dir Extension" ,
1230+ "version" : "1.0.0" ,
1231+ "description" : "Test" ,
1232+ },
1233+ "requires" : {"speckit_version" : ">=0.1.0" },
1234+ "provides" : {
1235+ "commands" : [
1236+ {
1237+ "name" : "speckit.ext-multidir.run" ,
1238+ "file" : "commands/run.md" ,
1239+ "description" : "Run command" ,
1240+ }
1241+ ]
1242+ },
1243+ }
1244+ with open (ext_dir / "extension.yml" , "w" ) as f :
1245+ yaml .dump (manifest_data , f )
1246+
1247+ (ext_dir / "commands" / "run.md" ).write_text (
1248+ "---\n "
1249+ "description: Run command\n "
1250+ "---\n \n "
1251+ "Read agents/control/commander.md for instructions.\n "
1252+ "Use templates/report.md as output format.\n "
1253+ "Run scripts/bash/gate.sh to validate.\n "
1254+ "Load knowledge-base/scores.yaml for calibration.\n "
1255+ "Also check memory/constitution.md for project rules.\n "
1256+ )
1257+
1258+ skills_dir = project_dir / ".agents" / "skills"
1259+ skills_dir .mkdir (parents = True )
1260+
1261+ manifest = ExtensionManifest (ext_dir / "extension.yml" )
1262+ registrar = CommandRegistrar ()
1263+ registrar .register_commands_for_agent ("codex" , manifest , ext_dir , project_dir )
1264+
1265+ content = (skills_dir / "speckit-ext-multidir-run" / "SKILL.md" ).read_text ()
1266+ # Extension-owned directories → extension-local paths
1267+ assert ".specify/extensions/ext-multidir/agents/control/commander.md" in content
1268+ assert ".specify/extensions/ext-multidir/templates/report.md" in content
1269+ assert ".specify/extensions/ext-multidir/scripts/bash/gate.sh" in content
1270+ assert ".specify/extensions/ext-multidir/knowledge-base/scores.yaml" in content
1271+ # memory/ is not an extension directory, so stays project-level
1272+ assert "memory/constitution.md" in content
1273+ # No bare extension-relative path references remain
1274+ assert "Read agents/" not in content
1275+ assert "Load knowledge-base/" not in content
1276+
1277+ def test_skill_registration_rewrites_extension_relative_paths_for_kimi (self , project_dir , temp_dir ):
1278+ """Path rewriting should also apply to kimi, which uses the /SKILL.md extension."""
1279+ import yaml
1280+
1281+ ext_dir = temp_dir / "ext-kimi-paths"
1282+ ext_dir .mkdir ()
1283+ (ext_dir / "commands" ).mkdir ()
1284+ (ext_dir / "agents" ).mkdir ()
1285+ (ext_dir / "knowledge-base" ).mkdir ()
1286+
1287+ manifest_data = {
1288+ "schema_version" : "1.0" ,
1289+ "extension" : {
1290+ "id" : "ext-kimi-paths" ,
1291+ "name" : "Kimi Paths Extension" ,
1292+ "version" : "1.0.0" ,
1293+ "description" : "Test" ,
1294+ },
1295+ "requires" : {"speckit_version" : ">=0.1.0" },
1296+ "provides" : {
1297+ "commands" : [
1298+ {
1299+ "name" : "speckit.ext-kimi-paths.run" ,
1300+ "file" : "commands/run.md" ,
1301+ "description" : "Run command" ,
1302+ }
1303+ ]
1304+ },
1305+ }
1306+ with open (ext_dir / "extension.yml" , "w" ) as f :
1307+ yaml .dump (manifest_data , f )
1308+
1309+ (ext_dir / "commands" / "run.md" ).write_text (
1310+ "---\n "
1311+ "description: Run command\n "
1312+ "---\n \n "
1313+ "Read agents/control/commander.md for instructions.\n "
1314+ "Load knowledge-base/scores.yaml for calibration.\n "
1315+ )
1316+
1317+ skills_dir = project_dir / ".kimi" / "skills"
1318+ skills_dir .mkdir (parents = True )
1319+
1320+ manifest = ExtensionManifest (ext_dir / "extension.yml" )
1321+ registrar = CommandRegistrar ()
1322+ registrar .register_commands_for_agent ("kimi" , manifest , ext_dir , project_dir )
1323+
1324+ content = (skills_dir / "speckit-ext-kimi-paths-run" / "SKILL.md" ).read_text ()
1325+ assert ".specify/extensions/ext-kimi-paths/agents/control/commander.md" in content
1326+ assert ".specify/extensions/ext-kimi-paths/knowledge-base/scores.yaml" in content
1327+ assert "Read agents/" not in content
1328+
1329+ def test_skill_registration_rewrites_paths_in_aliases (self , project_dir , temp_dir ):
1330+ """Alias SKILL.md files should also have extension-relative paths rewritten."""
1331+ import yaml
1332+
1333+ ext_dir = temp_dir / "ext-alias-paths"
1334+ ext_dir .mkdir ()
1335+ (ext_dir / "commands" ).mkdir ()
1336+ (ext_dir / "agents" ).mkdir ()
1337+
1338+ manifest_data = {
1339+ "schema_version" : "1.0" ,
1340+ "extension" : {
1341+ "id" : "ext-alias-paths" ,
1342+ "name" : "Alias Paths Extension" ,
1343+ "version" : "1.0.0" ,
1344+ "description" : "Test" ,
1345+ },
1346+ "requires" : {"speckit_version" : ">=0.1.0" },
1347+ "provides" : {
1348+ "commands" : [
1349+ {
1350+ "name" : "speckit.ext-alias-paths.run" ,
1351+ "file" : "commands/run.md" ,
1352+ "description" : "Run command" ,
1353+ "aliases" : ["speckit.ext-alias-paths.go" ],
1354+ }
1355+ ]
1356+ },
1357+ }
1358+ with open (ext_dir / "extension.yml" , "w" ) as f :
1359+ yaml .dump (manifest_data , f )
1360+
1361+ (ext_dir / "commands" / "run.md" ).write_text (
1362+ "---\n "
1363+ "description: Run command\n "
1364+ "---\n \n "
1365+ "Read agents/control/commander.md for instructions.\n "
1366+ )
1367+
1368+ skills_dir = project_dir / ".agents" / "skills"
1369+ skills_dir .mkdir (parents = True )
1370+
1371+ manifest = ExtensionManifest (ext_dir / "extension.yml" )
1372+ registrar = CommandRegistrar ()
1373+ registrar .register_commands_for_agent ("codex" , manifest , ext_dir , project_dir )
1374+
1375+ alias_content = (skills_dir / "speckit-ext-alias-paths-go" / "SKILL.md" ).read_text ()
1376+ assert ".specify/extensions/ext-alias-paths/agents/control/commander.md" in alias_content
1377+ assert "Read agents/" not in alias_content
1378+
1379+ def test_rewrite_extension_paths_no_subdirs (self , project_dir , temp_dir ):
1380+ """Extension with no subdirectories should leave command body text unchanged."""
1381+ import yaml
1382+
1383+ ext_dir = temp_dir / "bare-ext"
1384+ ext_dir .mkdir ()
1385+ (ext_dir / "commands" ).mkdir ()
1386+
1387+ manifest_data = {
1388+ "schema_version" : "1.0" ,
1389+ "extension" : {"id" : "bare-ext" , "name" : "Bare" , "version" : "1.0.0" , "description" : "Test" },
1390+ "requires" : {"speckit_version" : ">=0.1.0" },
1391+ "provides" : {"commands" : [{"name" : "speckit.bare-ext.run" , "file" : "commands/run.md" , "description" : "Run" }]},
1392+ }
1393+ with open (ext_dir / "extension.yml" , "w" ) as f :
1394+ yaml .dump (manifest_data , f )
1395+
1396+ (ext_dir / "commands" / "run.md" ).write_text (
1397+ "---\n description: Run\n ---\n \n Read agents/control/commander.md and templates/report.md.\n "
1398+ )
1399+
1400+ skills_dir = project_dir / ".agents" / "skills"
1401+ skills_dir .mkdir (parents = True )
1402+
1403+ manifest = ExtensionManifest (ext_dir / "extension.yml" )
1404+ CommandRegistrar ().register_commands_for_agent ("codex" , manifest , ext_dir , project_dir )
1405+
1406+ content = (skills_dir / "speckit-bare-ext-run" / "SKILL.md" ).read_text ()
1407+ # No subdirs to match — text unchanged
1408+ assert "agents/control/commander.md" in content
1409+ assert "templates/report.md" in content
1410+
12121411 def test_codex_skill_alias_frontmatter_matches_alias_name (self , project_dir , temp_dir ):
12131412 """Codex alias skills should render their own matching `name:` frontmatter."""
12141413 import yaml
0 commit comments