@clawplays/ospec-cli 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -224,7 +224,98 @@ const services_1 = require("./services");
224
224
 
225
225
 
226
226
 
227
- const CLI_VERSION = '0.1.1';
227
+ const CLI_VERSION = '0.3.0';
228
+
229
+ function showInitUsage() {
230
+ console.log('Usage: ospec init [root-dir] [--summary "..."] [--tech-stack node,react] [--architecture "..."] [--document-language zh-CN|en-US]');
231
+ }
232
+
233
+ function parseInitCommandArgs(commandArgs) {
234
+ let rootDir;
235
+ const options = {};
236
+ for (let index = 0; index < commandArgs.length; index += 1) {
237
+ const arg = commandArgs[index];
238
+ if (arg === '--help' || arg === '-h' || arg === 'help') {
239
+ showInitUsage();
240
+ process.exit(0);
241
+ }
242
+ if (arg === '--summary') {
243
+ const value = commandArgs[index + 1];
244
+ if (!value || value.startsWith('--')) {
245
+ console.error('Error: --summary requires a value');
246
+ showInitUsage();
247
+ process.exit(1);
248
+ }
249
+ options.summary = value.trim();
250
+ index += 1;
251
+ continue;
252
+ }
253
+ if (arg.startsWith('--summary=')) {
254
+ options.summary = arg.slice('--summary='.length).trim();
255
+ continue;
256
+ }
257
+ if (arg === '--tech-stack') {
258
+ const value = commandArgs[index + 1];
259
+ if (!value || value.startsWith('--')) {
260
+ console.error('Error: --tech-stack requires a comma-separated value');
261
+ showInitUsage();
262
+ process.exit(1);
263
+ }
264
+ options.techStack = value.split(',').map(item => item.trim()).filter(Boolean);
265
+ index += 1;
266
+ continue;
267
+ }
268
+ if (arg.startsWith('--tech-stack=')) {
269
+ options.techStack = arg.slice('--tech-stack='.length).split(',').map(item => item.trim()).filter(Boolean);
270
+ continue;
271
+ }
272
+ if (arg === '--architecture') {
273
+ const value = commandArgs[index + 1];
274
+ if (!value || value.startsWith('--')) {
275
+ console.error('Error: --architecture requires a value');
276
+ showInitUsage();
277
+ process.exit(1);
278
+ }
279
+ options.architecture = value.trim();
280
+ index += 1;
281
+ continue;
282
+ }
283
+ if (arg.startsWith('--architecture=')) {
284
+ options.architecture = arg.slice('--architecture='.length).trim();
285
+ continue;
286
+ }
287
+ if (arg === '--document-language' || arg === '--lang') {
288
+ const value = commandArgs[index + 1];
289
+ if (!value || value.startsWith('--')) {
290
+ console.error('Error: --document-language requires a value');
291
+ showInitUsage();
292
+ process.exit(1);
293
+ }
294
+ options.documentLanguage = value.trim();
295
+ index += 1;
296
+ continue;
297
+ }
298
+ if (arg.startsWith('--document-language=')) {
299
+ options.documentLanguage = arg.slice('--document-language='.length).trim();
300
+ continue;
301
+ }
302
+ if (arg.startsWith('--lang=')) {
303
+ options.documentLanguage = arg.slice('--lang='.length).trim();
304
+ continue;
305
+ }
306
+ if (!rootDir) {
307
+ rootDir = arg;
308
+ continue;
309
+ }
310
+ console.error(`Error: unexpected argument "${arg}"`);
311
+ showInitUsage();
312
+ process.exit(1);
313
+ }
314
+ return {
315
+ rootDir,
316
+ options,
317
+ };
318
+ }
228
319
 
229
320
 
230
321
 
@@ -432,7 +523,9 @@ async function main() {
432
523
 
433
524
 
434
525
 
435
- await initCmd.execute(commandArgs[0]);
526
+ const { rootDir, options } = parseInitCommandArgs(commandArgs);
527
+
528
+ await initCmd.execute(rootDir, options);
436
529
 
437
530
 
438
531
 
@@ -927,7 +1020,7 @@ OSpec CLI v${CLI_VERSION}
927
1020
  Usage: ospec <command> [options]
928
1021
 
929
1022
  Commands:
930
- init [root-dir] Initialize the OSpec protocol shell
1023
+ init [root-dir] Initialize OSpec to a change-ready state
931
1024
  new <change-name> [root] Create a new change (supports --flags)
932
1025
  verify [path] Verify change completion
933
1026
  progress [path] Show workflow progress
@@ -941,7 +1034,7 @@ Commands:
941
1034
  docs [action] [path] Docs helpers (status, generate)
942
1035
  skills [action] [path] Skills status helpers (status)
943
1036
  plugins [action] [path] Plugin helpers (list, status, enable, disable, approve, reject)
944
- skill [action] [skill] [dir] Skill package helpers (default skill is ospec-change)
1037
+ skill [action] [skill] [dir] Skill package helpers (managed skills: ospec, ospec-change)
945
1038
  index [action] [path] Index helpers (check, build)
946
1039
  workflow [action] Workflow configuration (show, list-flags)
947
1040
  update [path] Refresh protocol docs, tooling, hooks, and installed skills; does not enable or migrate plugins
@@ -950,6 +1043,7 @@ Commands:
950
1043
 
951
1044
  Examples:
952
1045
  ospec init
1046
+ ospec init . --summary "Internal admin portal" --tech-stack node,react,postgres
953
1047
  ospec new onboarding-flow
954
1048
  ospec new landing-refresh . --flags ui_change,page_design
955
1049
  ospec verify ./changes/active/onboarding-flow
@@ -972,11 +1066,13 @@ Examples:
972
1066
  ospec plugins enable checkpoint . --base-url http://127.0.0.1:3000
973
1067
  ospec plugins run checkpoint ./changes/active/onboarding-flow
974
1068
  ospec plugins approve stitch ./changes/active/onboarding-flow
975
- ospec skill status
976
- ospec skill install
1069
+ ospec skill status ospec
1070
+ ospec skill install ospec
1071
+ ospec skill status ospec-change
1072
+ ospec skill install ospec-change
977
1073
  ospec skill install ospec-init
978
- ospec skill status-claude
979
- ospec skill install-claude
1074
+ ospec skill status-claude ospec
1075
+ ospec skill install-claude ospec
980
1076
  ospec index build
981
1077
  ospec batch stats
982
1078
  ospec changes status
@@ -19,8 +19,8 @@ class DocsCommand extends BaseCommand_1.BaseCommand {
19
19
  throw new Error('Project is not initialized. Run "ospec init" first.');
20
20
  }
21
21
  const result = await services_1.services.projectService.generateProjectKnowledge(targetPath);
22
- console.log('\nProject Knowledge Generated');
23
- console.log('=========================\n');
22
+ console.log('\nProject Knowledge Synced');
23
+ console.log('======================\n');
24
24
  console.log(`Project: ${result.projectName}`);
25
25
  console.log(`Mode: ${result.mode}`);
26
26
  console.log(`Created files: ${result.createdFiles.length}`);
@@ -29,6 +29,7 @@ class DocsCommand extends BaseCommand_1.BaseCommand {
29
29
  console.log(`Direct-copy assets created: ${result.directCopyCreatedFiles.length}`);
30
30
  console.log(`Hooks installed: ${result.hookInstalledFiles.length}`);
31
31
  console.log(`Runtime-generated files: ${result.runtimeGeneratedFiles.join(', ') || '-'}`);
32
+ console.log('Purpose: refresh, repair, or backfill project knowledge docs after initialization');
32
33
  console.log('Business scaffold: not applied by docs generate');
33
34
  console.log('Bootstrap summary: not generated by docs generate');
34
35
  if (result.firstChangeSuggestion) {
@@ -34,32 +34,90 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.InitCommand = void 0;
37
+ const os_1 = require("os");
37
38
  const path = __importStar(require("path"));
38
39
  const services_1 = require("../services");
39
40
  const BaseCommand_1 = require("./BaseCommand");
41
+ const SkillCommand_1 = require("./SkillCommand");
40
42
  class InitCommand extends BaseCommand_1.BaseCommand {
41
- async execute(rootDir) {
43
+ async execute(rootDir, input = {}) {
42
44
  try {
43
45
  const targetDir = rootDir || process.cwd();
44
46
  this.logger.info(`Initializing OSpec project at ${targetDir}`);
45
47
  const structure = await services_1.services.projectService.detectProjectStructure(targetDir);
46
- if (structure.initialized) {
47
- this.warn('Project already initialized');
48
- return;
48
+ let protocolShellCreated = false;
49
+ if (!structure.initialized) {
50
+ const config = await services_1.services.configManager.createDefaultConfig('full');
51
+ await services_1.services.projectService.initializeProtocolShellProject(targetDir, config.mode, {
52
+ projectName: path.basename(targetDir),
53
+ ...input,
54
+ });
55
+ protocolShellCreated = true;
49
56
  }
50
- const config = await services_1.services.configManager.createDefaultConfig('full');
51
- await services_1.services.projectService.initializeProtocolShellProject(targetDir, config.mode, {
52
- projectName: path.basename(targetDir),
53
- });
54
- this.success(`${structure.initialized ? 'Protocol shell refreshed' : 'Protocol shell initialized'} at ${targetDir}`);
55
- this.info(` Created OSpec protocol shell for ${path.basename(targetDir)}`);
56
- this.info(' Added .skillrc, changes/, .ospec/, for-ai protocol docs, root SKILL.md, and SKILL.index.json');
57
+ else {
58
+ this.info(' Protocol shell already present; reconciling the repository to a change-ready state');
59
+ }
60
+ const result = await services_1.services.projectService.generateProjectKnowledge(targetDir, input);
61
+ const skillResult = await this.syncInstalledSkills();
62
+ this.success(`Project initialized to change-ready state at ${targetDir}`);
63
+ this.info(` Protocol shell: ${protocolShellCreated ? 'created' : 'already present'}`);
64
+ this.info(` Project knowledge: ${result.createdFiles.length} created, ${result.refreshedFiles.length} refreshed, ${result.skippedFiles.length} skipped`);
65
+ this.info(` Document language: ${result.documentLanguage}`);
66
+ this.info(` Codex skills: ${this.formatManagedSkills(skillResult.codex)}`);
67
+ if (skillResult.claude.length > 0) {
68
+ this.info(` Claude skills: ${this.formatManagedSkills(skillResult.claude)}`);
69
+ }
70
+ if (input.summary || (Array.isArray(input.techStack) && input.techStack.length > 0) || input.architecture) {
71
+ this.info(' Applied user-provided project context to the generated knowledge docs');
72
+ }
73
+ if (result.firstChangeSuggestion) {
74
+ this.info(` Suggested first change: ${result.firstChangeSuggestion.name}`);
75
+ }
76
+ this.info(` Next: ospec new <change-name> ${targetDir}`);
57
77
  }
58
78
  catch (error) {
59
79
  this.error(`Failed to initialize project: ${error}`);
60
80
  throw error;
61
81
  }
62
82
  }
83
+ getManagedSkillNames() {
84
+ return ['ospec', 'ospec-change'];
85
+ }
86
+ formatManagedSkills(results) {
87
+ return results.map(result => result.skillName).join(', ');
88
+ }
89
+ async syncInstalledSkills() {
90
+ const skillCommand = new SkillCommand_1.SkillCommand();
91
+ const codex = [];
92
+ for (const skillName of this.getManagedSkillNames()) {
93
+ codex.push(await skillCommand.installSkill('codex', skillName));
94
+ }
95
+ const claude = [];
96
+ if (await this.shouldSyncClaudeSkills()) {
97
+ for (const skillName of this.getManagedSkillNames()) {
98
+ claude.push(await skillCommand.installSkill('claude', skillName));
99
+ }
100
+ }
101
+ return { codex, claude };
102
+ }
103
+ resolveProviderHome(provider) {
104
+ const envHome = provider === 'claude'
105
+ ? String(process.env.CLAUDE_HOME || '').trim()
106
+ : String(process.env.CODEX_HOME || '').trim();
107
+ if (envHome) {
108
+ return path.resolve(envHome);
109
+ }
110
+ return provider === 'claude'
111
+ ? path.join((0, os_1.homedir)(), '.claude')
112
+ : path.join((0, os_1.homedir)(), '.codex');
113
+ }
114
+ async shouldSyncClaudeSkills() {
115
+ const claudeHome = this.resolveProviderHome('claude');
116
+ if (String(process.env.CLAUDE_HOME || '').trim()) {
117
+ return true;
118
+ }
119
+ return services_1.services.fileService.exists(claudeHome);
120
+ }
63
121
  }
64
122
  exports.InitCommand = InitCommand;
65
123
  //# sourceMappingURL=InitCommand.js.map
@@ -28,11 +28,11 @@ const ACTION_SKILLS = [
28
28
 
29
29
  title: 'OSpec Init',
30
30
 
31
- description: 'Initialize the OSpec protocol shell for a directory without assuming stack templates or creating the first change.',
31
+ description: 'Initialize an OSpec repository to change-ready state without creating the first change automatically.',
32
32
 
33
- shortDescription: 'Initialize OSpec protocol shell',
33
+ shortDescription: 'Initialize OSpec to change-ready',
34
34
 
35
- defaultPrompt: 'Use $ospec-init to inspect the target directory, run ospec status first, initialize the protocol shell with ospec init when needed, and verify the protocol-shell files on disk. Do not create docs backfill, business scaffold, or the first change automatically.',
35
+ defaultPrompt: 'Use $ospec-init to initialize the target directory with ospec init so the repository ends in change-ready state. Reuse existing project docs when available. If the repository lacks a usable project overview and you are in an AI-assisted flow, ask one concise question for project summary or tech stack before calling ospec init with those inputs; if the user declines, run plain ospec init and allow placeholder docs. Verify the protocol-shell files and project knowledge docs on disk. Do not create the first change automatically.',
36
36
 
37
37
  markdown: `# OSpec Init
38
38
 
@@ -46,15 +46,17 @@ Use this action when the user intent is initialization.
46
46
 
47
47
 
48
48
 
49
- - run \`ospec status [path]\` first
49
+ - use \`ospec init [path]\` so the repository ends in change-ready state
50
50
 
51
- - use \`ospec init [path]\` for plain initialization
51
+ - verify \`.skillrc\`, \`.ospec/\`, \`changes/\`, \`SKILL.md\`, \`SKILL.index.json\`, \`build-index-auto.cjs\`, \`for-ai/\`, and \`docs/project/\` files on disk
52
52
 
53
- - verify \`.skillrc\`, \`.ospec/\`, \`changes/\`, \`SKILL.md\`, \`SKILL.index.json\`, \`build-index-auto.cjs\`, and \`for-ai/\` files on disk
53
+ - if project overview context is missing and AI can ask follow-up questions, ask for a brief summary or tech stack before initialization; if the user declines, fall back to placeholder docs
54
+
55
+ - use \`ospec status [path]\` only when you want an explicit summary or troubleshooting snapshot
54
56
 
55
57
  - do not assume a web template when the project type is unclear
56
58
 
57
- - do not backfill docs or create the first change unless explicitly requested
59
+ - do not create the first change unless explicitly requested
58
60
 
59
61
 
60
62
 
@@ -64,10 +66,12 @@ Use this action when the user intent is initialization.
64
66
 
65
67
  \`\`\`bash
66
68
 
67
- ospec status [path]
68
-
69
69
  ospec init [path]
70
70
 
71
+ ospec init [path] --summary "..." --tech-stack node,react
72
+
73
+ ospec status [path]
74
+
71
75
  \`\`\`
72
76
 
73
77
  `,
@@ -130,17 +134,17 @@ ospec changes status [path]
130
134
 
131
135
  title: 'OSpec Backfill',
132
136
 
133
- description: 'Backfill the project knowledge layer after protocol-shell init without applying business scaffold or creating a change.',
137
+ description: 'Refresh or repair the project knowledge layer for an initialized repository without creating a change.',
134
138
 
135
- shortDescription: 'Backfill project knowledge layer',
139
+ shortDescription: 'Refresh project knowledge layer',
136
140
 
137
- defaultPrompt: 'Use $ospec-backfill to backfill the project knowledge layer after protocol-shell init. Prefer ospec docs generate, keep scaffold explicit, and do not create the first change automatically.',
141
+ defaultPrompt: 'Use $ospec-backfill to refresh, repair, or backfill the project knowledge layer for an initialized repository. Prefer ospec docs generate when you only need docs maintenance, keep scaffold explicit, and do not create the first change automatically.',
138
142
 
139
143
  markdown: `# OSpec Backfill
140
144
 
141
145
 
142
146
 
143
- Use this action after the protocol shell already exists and the repository still lacks project knowledge.
147
+ Use this action after the repository is already initialized and the project knowledge docs need maintenance.
144
148
 
145
149
 
146
150
 
@@ -148,7 +152,7 @@ Use this action after the protocol shell already exists and the repository still
148
152
 
149
153
 
150
154
 
151
- - require the protocol shell first
155
+ - require an initialized repository first
152
156
 
153
157
  - prefer \`ospec docs generate [path]\`
154
158
 
@@ -787,7 +791,7 @@ class SkillCommand extends BaseCommand_1.BaseCommand {
787
791
 
788
792
  title: 'OSpec',
789
793
 
790
- description: 'Protocol-shell-first OSpec workflow for initialization, project knowledge backfill, change execution, verification, and archive readiness.',
794
+ description: 'Protocol-shell-first OSpec workflow for inspection, change-ready initialization, docs maintenance, change execution, verification, and archive readiness.',
791
795
 
792
796
  shortDescription: 'Inspect, initialize, and operate OSpec projects',
793
797
 
@@ -1025,7 +1029,7 @@ ${markdownBody.trimStart()}`;
1025
1029
 
1026
1030
  if (/^---\r?\n/.test(definition.markdown)) {
1027
1031
 
1028
- return definition.markdown;
1032
+ return this.ensureFrontmatterDescription(definition.markdown, definition.description);
1029
1033
 
1030
1034
  }
1031
1035
 
@@ -1045,6 +1049,24 @@ ${definition.markdown.trimStart()}`;
1045
1049
 
1046
1050
  }
1047
1051
 
1052
+ ensureFrontmatterDescription(markdown, description) {
1053
+
1054
+ if (!/^---\r?\n/.test(markdown)) {
1055
+
1056
+ return markdown;
1057
+
1058
+ }
1059
+
1060
+ if (/^---\r?\n[\s\S]*?\r?\ndescription:\s+/m.test(markdown)) {
1061
+
1062
+ return markdown;
1063
+
1064
+ }
1065
+
1066
+ return markdown.replace(/^---\r?\n/, `---\ndescription: ${description}\n`);
1067
+
1068
+ }
1069
+
1048
1070
  escapeYaml(value) {
1049
1071
 
1050
1072
  return value.replace(/"/g, '\\"');
@@ -1091,7 +1113,7 @@ Prefer this prompt style for new work:
1091
1113
 
1092
1114
  2. \`Use ospec to inspect this repository\`
1093
1115
 
1094
- 3. \`Use ospec to backfill the project knowledge layer\`
1116
+ 3. \`Use ospec to refresh or repair the project knowledge layer\`
1095
1117
 
1096
1118
  4. \`Use ospec to create and advance a change for this requirement\`
1097
1119
 
@@ -1157,7 +1179,7 @@ interface:
1157
1179
 
1158
1180
  short_description: "Legacy alias for the OSpec skill"
1159
1181
 
1160
- default_prompt: "Use $ospec to inspect and initialize this directory according to OSpec rules: protocol shell first, explicit knowledge backfill, no assumed web template when the project type is unclear, and no automatic first change."
1182
+ default_prompt: "Use $ospec to initialize this directory according to OSpec rules: init should end in change-ready state, reuse existing docs when available, ask for missing summary or tech stack in AI-assisted flows before falling back to placeholder docs, avoid assumed web templates when the project type is unclear, and do not create the first change automatically."
1161
1183
 
1162
1184
  `,
1163
1185
 
@@ -1167,7 +1189,7 @@ interface:
1167
1189
 
1168
1190
  short_description: "Legacy alias for the OSpec skill"
1169
1191
 
1170
- default_prompt: "Use $ospec to inspect and initialize this directory according to OSpec rules: protocol shell first, explicit knowledge backfill, no assumed web template when the project type is unclear, and no automatic first change."
1192
+ default_prompt: "Use $ospec to initialize this directory according to OSpec rules: init should end in change-ready state, reuse existing docs when available, ask for missing summary or tech stack in AI-assisted flows before falling back to placeholder docs, avoid assumed web templates when the project type is unclear, and do not create the first change automatically."
1171
1193
 
1172
1194
  `,
1173
1195
 
@@ -8,22 +8,40 @@ class StatusCommand extends BaseCommand_1.BaseCommand {
8
8
  try {
9
9
  const targetPath = projectPath || process.cwd();
10
10
  this.logger.info(`Getting status for ${targetPath}`);
11
- const [structure, summary, docs, skills, execution, changes, queuedChanges, runReport] = await Promise.all([
12
- services_1.services.projectService.detectProjectStructure(targetPath),
11
+ const structure = await services_1.services.projectService.detectProjectStructure(targetPath);
12
+ const [summary, docs, skills, queuedChanges] = await Promise.all([
13
13
  services_1.services.projectService.getProjectSummary(targetPath),
14
14
  services_1.services.projectService.getDocsStatus(targetPath),
15
15
  services_1.services.projectService.getSkillsStatus(targetPath),
16
- services_1.services.projectService.getExecutionStatus(targetPath),
17
- services_1.services.projectService.getActiveChangeStatusReport(targetPath),
18
16
  services_1.services.queueService.getQueuedChanges(targetPath),
19
- services_1.services.runService.getStatusReport(targetPath),
20
17
  ]);
18
+ let execution = {
19
+ totalActiveChanges: summary.activeChangeCount,
20
+ byStatus: {},
21
+ activeChanges: [],
22
+ };
23
+ let changes = {
24
+ totals: { pass: 0, warn: 0, fail: 0 },
25
+ };
26
+ let runReport = {
27
+ currentRun: undefined,
28
+ stage: undefined,
29
+ nextInstruction: undefined,
30
+ };
31
+ if (structure.initialized) {
32
+ [execution, changes, runReport] = await Promise.all([
33
+ services_1.services.projectService.getExecutionStatus(targetPath),
34
+ services_1.services.projectService.getActiveChangeStatusReport(targetPath),
35
+ services_1.services.runService.getStatusReport(targetPath),
36
+ ]);
37
+ }
21
38
  console.log('\nProject Status');
22
39
  console.log('==============\n');
23
40
  console.log(`Name: ${summary.name}`);
24
41
  console.log(`Path: ${summary.path}`);
25
42
  console.log(`Mode: ${summary.mode ?? 'uninitialized'}`);
26
43
  console.log(`Initialized: ${summary.initialized ? 'yes' : 'no'}`);
44
+ console.log(`Change Ready: ${summary.initialized && docs.missingRequired.length === 0 ? 'yes' : 'no'}`);
27
45
  console.log(`Structure Level: ${summary.structureLevel}`);
28
46
  console.log(`Active Changes: ${summary.activeChangeCount}`);
29
47
  console.log(`Queued Changes: ${queuedChanges.length}`);
@@ -104,13 +122,14 @@ class StatusCommand extends BaseCommand_1.BaseCommand {
104
122
  getRecommendedNextSteps(projectPath, structure, docs, execution, queuedChanges, runReport) {
105
123
  if (!structure.initialized) {
106
124
  return [
107
- `Run "ospec init ${projectPath}" to initialize the OSpec protocol shell.`,
125
+ `Run "ospec init ${projectPath}" to initialize the repository to a change-ready state.`,
108
126
  ];
109
127
  }
110
128
  if (docs.missingRequired.length > 0 || docs.coverage < 100) {
111
129
  return [
112
- 'The OSpec protocol shell is ready, but the project knowledge layer is still incomplete.',
113
- `Run "ospec docs generate ${projectPath}" to backfill the default project knowledge layer. This still will not apply business scaffold or generate docs/project/bootstrap-summary.md.`,
130
+ 'The repository is initialized, but the project knowledge layer is still incomplete.',
131
+ `Run "ospec init ${projectPath}" to reconcile the repository back to change-ready state and regenerate missing project knowledge docs.`,
132
+ `If you only want to refresh or repair docs without rerunning full init messaging, use "ospec docs generate ${projectPath}".`,
114
133
  ];
115
134
  }
116
135
  if (execution.totalActiveChanges === 0 && queuedChanges.length === 0) {
@@ -30,9 +30,9 @@ class UpdateCommand extends BaseCommand_1.BaseCommand {
30
30
  if (toolingResult.hookInstalledFiles.length > 0) {
31
31
  this.info(` git hooks refreshed: ${toolingResult.hookInstalledFiles.join(', ')}`);
32
32
  }
33
- this.info(` codex skill: ${skillResult.codex.targetDir}`);
34
- if (skillResult.claude) {
35
- this.info(` claude skill: ${skillResult.claude.targetDir}`);
33
+ this.info(` codex skills: ${this.formatManagedSkills(skillResult.codex)}`);
34
+ if (skillResult.claude.length > 0) {
35
+ this.info(` claude skills: ${this.formatManagedSkills(skillResult.claude)}`);
36
36
  }
37
37
  if (pluginResult.enabledPlugins.length > 0) {
38
38
  this.info(` plugin assets refreshed: ${pluginResult.enabledPlugins.join(', ')}`);
@@ -222,10 +222,24 @@ class UpdateCommand extends BaseCommand_1.BaseCommand {
222
222
  }
223
223
  return { createdFiles, skippedFiles };
224
224
  }
225
+ getManagedSkillNames() {
226
+ return ['ospec', 'ospec-change'];
227
+ }
228
+ formatManagedSkills(results) {
229
+ return results.map(result => result.skillName).join(', ');
230
+ }
225
231
  async syncInstalledSkills() {
226
232
  const skillCommand = new SkillCommand_1.SkillCommand();
227
- const codex = await skillCommand.installSkill('codex', 'ospec-change');
228
- const claude = (await this.shouldSyncClaudeSkills()) ? await skillCommand.installSkill('claude', 'ospec-change') : null;
233
+ const codex = [];
234
+ for (const skillName of this.getManagedSkillNames()) {
235
+ codex.push(await skillCommand.installSkill('codex', skillName));
236
+ }
237
+ const claude = [];
238
+ if (await this.shouldSyncClaudeSkills()) {
239
+ for (const skillName of this.getManagedSkillNames()) {
240
+ claude.push(await skillCommand.installSkill('claude', skillName));
241
+ }
242
+ }
229
243
  return { codex, claude };
230
244
  }
231
245
  resolveProviderHome(provider) {