@clawplays/ospec-cli 0.3.5 → 0.3.8

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/README.md CHANGED
@@ -31,21 +31,24 @@ OSpec is a document-driven workflow for AI-assisted development, helping you def
31
31
  <a href="https://github.com/clawplays/ospec/issues">Issues</a>
32
32
  </p>
33
33
 
34
- ## Install With npm
34
+ ## Why OSpec?
35
35
 
36
- ```bash
37
- npm install -g @clawplays/ospec-cli
38
- ```
36
+ AI coding assistants are powerful, but requirements that live only in chat history are hard to inspect, review, and close out cleanly. OSpec adds a lightweight workflow layer so the repository can hold the change context before code is written and after the work ships.
39
37
 
40
- Update inside your project directory:
38
+ - Align before code — keep proposal, tasks, state, verification, and review visible in the repo
39
+ - Keep each requirement explicit — the default path moves one requirement through one active change
40
+ - Stay lightweight — keep the normal flow short with `init -> change -> verify/finalize`
41
+ - Use the assistants you already have — OSpec is built for Codex, Claude Code, and direct CLI workflows
42
+
43
+ ## Install With npm
41
44
 
42
45
  ```bash
43
- ospec update
46
+ npm install -g @clawplays/ospec-cli
44
47
  ```
45
48
 
46
- ## Recommended Prompts
49
+ ## Quick Start
47
50
 
48
- Most teams only need 3 steps to use OSpec:
51
+ OSpec only takes 3 steps:
49
52
 
50
53
  1. initialize OSpec in your project directory
51
54
  2. create and advance one change for a requirement, document update, or bug fix
@@ -56,13 +59,13 @@ Most teams only need 3 steps to use OSpec:
56
59
  Recommended prompt:
57
60
 
58
61
  ```text
59
- Use OSpec to initialize this project.
62
+ OSpec, initialize this project.
60
63
  ```
61
64
 
62
65
  Claude / Codex skill mode:
63
66
 
64
67
  ```text
65
- Use $ospec to initialize this project.
68
+ $ospec initialize this project.
66
69
  ```
67
70
 
68
71
  <details>
@@ -96,17 +99,15 @@ Use this for requirement delivery, documentation updates, refactors, and bug fix
96
99
  Recommended prompt:
97
100
 
98
101
  ```text
99
- Use OSpec to create and advance a change for this requirement.
102
+ OSpec, create and advance a change for this requirement.
100
103
  ```
101
104
 
102
105
  Claude / Codex skill mode:
103
106
 
104
107
  ```text
105
- Use $ospec-change to create and advance a change for this requirement.
108
+ $ospec-change create and advance a change for this requirement.
106
109
  ```
107
110
 
108
- ![OSpec Change slash command example](docs/assets/ospecchange-slash-command.en.svg)
109
-
110
111
  <details>
111
112
  <summary>Command line</summary>
112
113
 
@@ -125,13 +126,13 @@ After the requirement has passed deployment, testing, QA, or other acceptance ch
125
126
  Recommended prompt:
126
127
 
127
128
  ```text
128
- Use OSpec to archive this accepted change.
129
+ OSpec, archive this accepted change.
129
130
  ```
130
131
 
131
132
  Claude / Codex skill mode:
132
133
 
133
134
  ```text
134
- Use $ospec to archive this accepted change.
135
+ $ospec archive this accepted change.
135
136
  ```
136
137
 
137
138
  <details>
@@ -151,12 +152,20 @@ Archive notes:
151
152
 
152
153
  </details>
153
154
 
155
+ ## Update With npm
156
+
157
+ For an existing OSpec project, after upgrading the CLI with npm, run this in the project directory to refresh the project's OSpec files:
158
+
159
+ ```bash
160
+ ospec update
161
+ ```
162
+
154
163
  ## How The OSpec Workflow Works
155
164
 
156
165
  ```text
157
166
  ┌─────────────────────────────────────────────────────────────────┐
158
167
  │ 1. USER REQUEST │
159
- │ "Use OSpec to create and advance a change for this task."
168
+ │ "OSpec, create and advance a change for this task."
160
169
  └─────────────────────────────────────────────────────────────────┘
161
170
 
162
171
 
@@ -224,13 +233,13 @@ Use Stitch for page design review and preview collaboration, especially for land
224
233
  AI conversation:
225
234
 
226
235
  ```text
227
- Use OSpec to enable the Stitch plugin.
236
+ OSpec, enable the Stitch plugin and connect using Codex/Gemini.
228
237
  ```
229
238
 
230
239
  Claude / Codex skill mode:
231
240
 
232
241
  ```text
233
- Use $ospec to enable the Stitch plugin.
242
+ $ospec enable the Stitch plugin and connect using Codex/Gemini.
234
243
  ```
235
244
 
236
245
  <details>
@@ -249,13 +258,13 @@ Use Checkpoint for app flow validation and automated checks, especially for subm
249
258
  AI conversation:
250
259
 
251
260
  ```text
252
- Use OSpec to enable the Checkpoint plugin.
261
+ OSpec, enable the Checkpoint plugin.
253
262
  ```
254
263
 
255
264
  Claude / Codex skill mode:
256
265
 
257
266
  ```text
258
- Use $ospec to enable the Checkpoint plugin.
267
+ $ospec enable the Checkpoint plugin.
259
268
  ```
260
269
 
261
270
  <details>
package/dist/cli.js CHANGED
@@ -224,7 +224,7 @@ const services_1 = require("./services");
224
224
 
225
225
 
226
226
 
227
- const CLI_VERSION = '0.3.5';
227
+ const CLI_VERSION = '0.3.8';
228
228
 
229
229
  function showInitUsage() {
230
230
  console.log('Usage: ospec init [root-dir] [--summary "..."] [--tech-stack node,react] [--architecture "..."] [--document-language en-US|zh-CN|ja-JP|ar]');
@@ -209,6 +209,7 @@ class ArchiveCommand extends BaseCommand_1.BaseCommand {
209
209
  await services_1.services.fileService.move(targetPath, archivePath);
210
210
  await services_1.services.stateManager.writeState(archivePath, nextState);
211
211
  await this.updateProposalStatus(archivePath, 'archived');
212
+ await services_1.services.projectService.rebaseMovedChangeMarkdownLinks(targetPath, archivePath);
212
213
  await services_1.services.projectService.rebuildIndex(projectRoot);
213
214
  return this.toRelativePath(projectRoot, archivePath);
214
215
  }
@@ -21,6 +21,9 @@ class ChangesCommand extends BaseCommand_1.BaseCommand {
21
21
  console.log(`Total: ${report.totalActiveChanges}`);
22
22
  console.log(`Queued: ${queuedChanges.length}`);
23
23
  console.log(`PASS ${report.totals.pass} | WARN ${report.totals.warn} | FAIL ${report.totals.fail}`);
24
+ if (report.totalActiveChanges > 1) {
25
+ console.log('WORKFLOW WARN multiple active changes detected. The default workflow expects one active change, and queue runner commands will fail until the repository is back to single-active mode.');
26
+ }
24
27
  console.log('');
25
28
  if (report.changes.length === 0) {
26
29
  console.log('No active changes.');
@@ -37,6 +37,7 @@ exports.InitCommand = void 0;
37
37
  const os_1 = require("os");
38
38
  const path = __importStar(require("path"));
39
39
  const services_1 = require("../services");
40
+ const helpers_1 = require("../utils/helpers");
40
41
  const BaseCommand_1 = require("./BaseCommand");
41
42
  const SkillCommand_1 = require("./SkillCommand");
42
43
  class InitCommand extends BaseCommand_1.BaseCommand {
@@ -73,7 +74,7 @@ class InitCommand extends BaseCommand_1.BaseCommand {
73
74
  if (result.firstChangeSuggestion) {
74
75
  this.info(` Suggested first change: ${result.firstChangeSuggestion.name}`);
75
76
  }
76
- this.info(` Next: ospec new <change-name> ${targetDir}`);
77
+ this.info(` Next: ${(0, helpers_1.formatCliCommand)('ospec', 'new', '<change-name>', targetDir)}`);
77
78
  }
78
79
  catch (error) {
79
80
  this.error(`Failed to initialize project: ${error}`);
@@ -37,6 +37,7 @@ exports.NewCommand = void 0;
37
37
  const path = __importStar(require("path"));
38
38
  const constants_1 = require("../core/constants");
39
39
  const services_1 = require("../services");
40
+ const helpers_1 = require("../utils/helpers");
40
41
  const PathUtils_1 = require("../utils/PathUtils");
41
42
  const PluginWorkflowComposer_1 = require("../workflow/PluginWorkflowComposer");
42
43
  const BaseCommand_1 = require("./BaseCommand");
@@ -50,6 +51,7 @@ class NewCommand extends BaseCommand_1.BaseCommand {
50
51
  const featureDir = PathUtils_1.PathUtils.getChangeDir(targetDir, placement, featureName);
51
52
  this.logger.info(`Creating ${placement === constants_1.DIR_NAMES.QUEUED ? 'queued change' : 'change'}: ${featureName}`);
52
53
  await this.ensureChangeNameAvailable(targetDir, featureName);
54
+ await this.ensureSingleActiveMode(targetDir, placement, featureName);
53
55
  await services_1.services.fileService.ensureDir(path.join(targetDir, constants_1.DIR_NAMES.CHANGES, placement));
54
56
  await services_1.services.fileService.ensureDir(featureDir);
55
57
  const config = await services_1.services.configManager.loadConfig(targetDir);
@@ -76,6 +78,8 @@ class NewCommand extends BaseCommand_1.BaseCommand {
76
78
  flags,
77
79
  optionalSteps: activatedSteps,
78
80
  documentLanguage,
81
+ projectRoot: targetDir,
82
+ documentPath: path.join(featureDir, constants_1.FILE_NAMES.PROPOSAL),
79
83
  }));
80
84
  await services_1.services.fileService.writeFile(path.join(featureDir, constants_1.FILE_NAMES.TASKS), services_1.services.templateEngine.generateTasksTemplate({
81
85
  feature: featureName,
@@ -85,6 +89,8 @@ class NewCommand extends BaseCommand_1.BaseCommand {
85
89
  flags,
86
90
  optionalSteps: activatedSteps,
87
91
  documentLanguage,
92
+ projectRoot: targetDir,
93
+ documentPath: path.join(featureDir, constants_1.FILE_NAMES.TASKS),
88
94
  }));
89
95
  await services_1.services.fileService.writeFile(path.join(featureDir, constants_1.FILE_NAMES.VERIFICATION), services_1.services.templateEngine.generateVerificationTemplate({
90
96
  feature: featureName,
@@ -94,6 +100,8 @@ class NewCommand extends BaseCommand_1.BaseCommand {
94
100
  flags,
95
101
  optionalSteps: activatedSteps,
96
102
  documentLanguage,
103
+ projectRoot: targetDir,
104
+ documentPath: path.join(featureDir, constants_1.FILE_NAMES.VERIFICATION),
97
105
  }));
98
106
  await services_1.services.fileService.writeFile(path.join(featureDir, constants_1.FILE_NAMES.REVIEW), services_1.services.templateEngine.generateReviewTemplate({
99
107
  feature: featureName,
@@ -103,6 +111,8 @@ class NewCommand extends BaseCommand_1.BaseCommand {
103
111
  flags,
104
112
  optionalSteps: activatedSteps,
105
113
  documentLanguage,
114
+ projectRoot: targetDir,
115
+ documentPath: path.join(featureDir, constants_1.FILE_NAMES.REVIEW),
106
116
  }));
107
117
  await this.writePluginArtifacts(featureDir, activatedSteps);
108
118
  this.success(`${placement === constants_1.DIR_NAMES.QUEUED ? 'Queued change' : 'Change'} ${featureName} created at ${featureDir}`);
@@ -191,28 +201,28 @@ class NewCommand extends BaseCommand_1.BaseCommand {
191
201
  if (/[\u0600-\u06FF]/.test(content)) {
192
202
  return 'ar';
193
203
  }
194
- if (/[ぁ-ゟ゠-ヿ]/.test(content)) {
204
+ if (this.hasJapaneseKana(content)) {
195
205
  return 'ja-JP';
196
206
  }
197
- if (this.isLikelyJapaneseKanjiContent(content)) {
198
- return 'ja-JP';
199
- }
200
- if (/[一-龥]/.test(content)) {
201
- return 'zh-CN';
207
+ if (this.hasCjkIdeographs(content)) {
208
+ return this.isLikelyJapaneseKanjiContent(content) ? 'ja-JP' : 'zh-CN';
202
209
  }
203
210
  if (/[A-Za-z]/.test(content)) {
204
211
  return 'en-US';
205
212
  }
206
213
  return null;
207
214
  }
215
+ hasJapaneseKana(content) {
216
+ return /[\u3040-\u30FF]/.test(content);
217
+ }
218
+ hasCjkIdeographs(content) {
219
+ return /[\u3400-\u9FFF]/.test(content);
220
+ }
208
221
  isLikelyJapaneseKanjiContent(content) {
209
- if (!/[一-龥]/.test(content)) {
222
+ if (!this.hasCjkIdeographs(content)) {
210
223
  return false;
211
224
  }
212
- if (/[々〆ヵヶ「」『』]/.test(content)) {
213
- return true;
214
- }
215
- return /(一覧|詳細|設定|権限|検索|構成|変更|確認|対応|連携|承認|申請|手順|履歴|機能|実装|設計|運用|画面|帳票|組織|拠点|区分|種別|完了|開始|終了|表示|取得|追加|削除|更新|登録)/.test(content);
225
+ return /[\u3005\u3006\u300C-\u300F\u30F5\u30F6]/.test(content);
216
226
  }
217
227
  async ensureChangeNameAvailable(targetDir, featureName) {
218
228
  const activeDir = PathUtils_1.PathUtils.getChangeDir(targetDir, constants_1.DIR_NAMES.ACTIVE, featureName);
@@ -229,6 +239,24 @@ class NewCommand extends BaseCommand_1.BaseCommand {
229
239
  }
230
240
  throw new Error(`Change ${featureName} already exists in ${conflicts.join(' and ')}. Continue the existing change instead of creating a duplicate.`);
231
241
  }
242
+ async ensureSingleActiveMode(targetDir, placement, featureName) {
243
+ if (placement !== constants_1.DIR_NAMES.ACTIVE) {
244
+ return;
245
+ }
246
+ const activeNames = await services_1.services.projectService.listActiveChangeNames(targetDir);
247
+ if (activeNames.length === 0) {
248
+ return;
249
+ }
250
+ if (activeNames.length === 1) {
251
+ const activeName = activeNames[0];
252
+ const activeChangePath = path.join(targetDir, constants_1.DIR_NAMES.CHANGES, constants_1.DIR_NAMES.ACTIVE, activeName);
253
+ const progressCommand = (0, helpers_1.formatCliCommand)('ospec', 'progress', activeChangePath);
254
+ const queueCommand = (0, helpers_1.formatCliCommand)('ospec', 'queue', 'add', featureName, targetDir);
255
+ throw new Error(`A single active change is the default workflow, but "${activeName}" is already active. Continue it with "${progressCommand}" or create queued work explicitly with "${queueCommand}".`);
256
+ }
257
+ const queueCommand = (0, helpers_1.formatCliCommand)('ospec', 'queue', 'add', featureName, targetDir);
258
+ throw new Error(`A single active change is the default workflow, but ${activeNames.length} active changes already exist: ${activeNames.join(', ')}. Resolve the repository back to one active change before creating another, or add new work with "${queueCommand}".`);
259
+ }
232
260
  async writePluginArtifacts(featureDir, activatedSteps) {
233
261
  const checkpointSteps = activatedSteps.filter(step => step === 'checkpoint_ui_review' || step === 'checkpoint_flow_check');
234
262
  if (checkpointSteps.length > 0) {
@@ -5,6 +5,7 @@ const child_process_1 = require("child_process");
5
5
  const path = require("path");
6
6
  const gray_matter_1 = require("gray-matter");
7
7
  const constants_1 = require("../core/constants");
8
+ const helpers_1 = require("../utils/helpers");
8
9
  const BaseCommand_1 = require("./BaseCommand");
9
10
  const services_1 = require("../services");
10
11
  const subcommandHelp_1 = require("../utils/subcommandHelp");
@@ -162,7 +163,7 @@ class PluginsCommand extends BaseCommand_1.BaseCommand {
162
163
  if (plugin.runner.extraEnvCount > 0) {
163
164
  console.log(` Extra env entries: ${plugin.runner.extraEnvCount}`);
164
165
  }
165
- console.log(` Doctor: ospec plugins doctor ${plugin.name} ${projectPath}`);
166
+ console.log(` Doctor: ${(0, helpers_1.formatCliCommand)('ospec', 'plugins', 'doctor', plugin.name, projectPath)}`);
166
167
  }
167
168
  console.log();
168
169
  });
@@ -231,7 +232,7 @@ class PluginsCommand extends BaseCommand_1.BaseCommand {
231
232
  this.info(` codex model: ${this.getStitchCodexConfig(nextConfig.plugins.stitch).model || '(cli default)'}`);
232
233
  if (enabled) {
233
234
  this.info(` token env: ${nextConfig.plugins.stitch.runner.token_env || '(not required)'}`);
234
- this.info(` doctor: ospec plugins doctor stitch ${projectPath}`);
235
+ this.info(` doctor: ${(0, helpers_1.formatCliCommand)('ospec', 'plugins', 'doctor', 'stitch', projectPath)}`);
235
236
  }
236
237
  this.info(' Affects new changes by default; update existing changes manually if needed');
237
238
  return;
@@ -311,7 +312,7 @@ class PluginsCommand extends BaseCommand_1.BaseCommand {
311
312
  this.info(` runner.command: ${nextConfig.plugins.checkpoint.runner.command || '(built-in adapter)'}`);
312
313
  this.info(` stitch integration: ${nextConfig.plugins.checkpoint.stitch_integration.enabled ? 'enabled' : 'disabled'}`);
313
314
  if (enabled) {
314
- this.info(` doctor: ospec plugins doctor checkpoint ${projectPath}`);
315
+ this.info(` doctor: ${(0, helpers_1.formatCliCommand)('ospec', 'plugins', 'doctor', 'checkpoint', projectPath)}`);
315
316
  }
316
317
  this.info(' Affects new changes by default; update existing changes manually if needed');
317
318
  return;
@@ -16,6 +16,8 @@ const path_1 = __importDefault(require("path"));
16
16
 
17
17
  const services_1 = require("../services");
18
18
 
19
+ const helpers_1 = require("../utils/helpers");
20
+
19
21
  const subcommandHelp_1 = require("../utils/subcommandHelp");
20
22
 
21
23
  const BaseCommand_1 = require("./BaseCommand");
@@ -554,7 +556,7 @@ class SkillCommand extends BaseCommand_1.BaseCommand {
554
556
 
555
557
  if (!result.inSync) {
556
558
 
557
- console.log(`\nRecommendation: run "ospec skill ${this.getInstallAction(provider)} ${result.skillName}${selection.targetDir ? ` ${selection.targetDir}` : ''}" to sync this skill.`);
559
+ console.log(`\nRecommendation: run "${(0, helpers_1.formatCliCommand)('ospec', 'skill', this.getInstallAction(provider), result.skillName, selection.targetDir)}" to sync this skill.`);
558
560
 
559
561
  }
560
562
 
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StatusCommand = void 0;
4
+ const path = require("path");
4
5
  const services_1 = require("../services");
6
+ const helpers_1 = require("../utils/helpers");
5
7
  const BaseCommand_1 = require("./BaseCommand");
6
8
  class StatusCommand extends BaseCommand_1.BaseCommand {
7
9
  async execute(projectPath) {
@@ -85,6 +87,9 @@ class StatusCommand extends BaseCommand_1.BaseCommand {
85
87
  console.log(` ${status}: ${count}`);
86
88
  }
87
89
  console.log(`Protocol summary: PASS ${changes.totals.pass} | WARN ${changes.totals.warn} | FAIL ${changes.totals.fail}`);
90
+ if (execution.totalActiveChanges > 1) {
91
+ console.log('Workflow warning: multiple active changes detected. The default workflow expects one active change unless you are explicitly managing extra work as queued changes.');
92
+ }
88
93
  if (execution.activeChanges.length > 0) {
89
94
  console.log('\nCurrent changes:');
90
95
  for (const change of execution.activeChanges) {
@@ -120,37 +125,46 @@ class StatusCommand extends BaseCommand_1.BaseCommand {
120
125
  }
121
126
  }
122
127
  getRecommendedNextSteps(projectPath, structure, docs, execution, queuedChanges, runReport) {
128
+ const formatCommand = (...args) => (0, helpers_1.formatCliCommand)('ospec', ...args);
123
129
  if (!structure.initialized) {
124
130
  return [
125
- `Run "ospec init ${projectPath}" to initialize the repository to a change-ready state.`,
131
+ `Run "${formatCommand('init', projectPath)}" to initialize the repository to a change-ready state.`,
126
132
  ];
127
133
  }
128
134
  if (docs.missingRequired.length > 0 || docs.coverage < 100) {
129
135
  return [
130
136
  '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}".`,
137
+ `Run "${formatCommand('init', projectPath)}" to reconcile the repository back to change-ready state and regenerate missing project knowledge docs.`,
138
+ `If you only want to refresh or repair docs without rerunning full init messaging, use "${formatCommand('docs', 'generate', projectPath)}".`,
133
139
  ];
134
140
  }
135
141
  if (execution.totalActiveChanges === 0 && queuedChanges.length === 0) {
136
142
  return [
137
- `Or run "ospec new <change-name> ${projectPath}" if you want to create the first change from CLI.`,
143
+ `Or run "${formatCommand('new', '<change-name>', projectPath)}" if you want to create the first change from CLI.`,
138
144
  ];
139
145
  }
140
146
  if (execution.totalActiveChanges === 0 && queuedChanges.length > 0) {
141
147
  return [
142
148
  `There is no active change right now, but ${queuedChanges.length} queued change(s) are waiting.`,
143
- `Run "ospec queue next ${projectPath}" if you want to activate the next queued change manually.`,
144
- `Or run "ospec run start ${projectPath}" to begin explicit queue tracking.`,
149
+ `Run "${formatCommand('queue', 'next', projectPath)}" if you want to activate the next queued change manually.`,
150
+ `Or run "${formatCommand('run', 'start', projectPath)}" to begin explicit queue tracking.`,
151
+ ];
152
+ }
153
+ if (execution.totalActiveChanges > 1) {
154
+ return [
155
+ `Multiple active changes are present. The default workflow expects one active change, but ${execution.totalActiveChanges} were found.`,
156
+ `Resolve the repository back to a single active change before using "${formatCommand('run', 'start', projectPath)}".`,
157
+ `For additional work, create queued changes explicitly with "${formatCommand('queue', 'add', '<change-name>', projectPath)}".`,
145
158
  ];
146
159
  }
147
160
  const currentChange = execution.activeChanges[0];
161
+ const currentChangePath = path.join(projectPath, 'changes', 'active', currentChange.name);
148
162
  const nextSteps = [
149
- `Continue the active change "${currentChange.name}" with "ospec progress ${projectPath}/changes/active/${currentChange.name}".`,
150
- `Run "ospec verify ${projectPath}/changes/active/${currentChange.name}" before trying to archive it.`,
163
+ `Continue the active change "${currentChange.name}" with "${formatCommand('progress', currentChangePath)}".`,
164
+ `Run "${formatCommand('verify', currentChangePath)}" before trying to archive it.`,
151
165
  ];
152
166
  if (queuedChanges.length > 0) {
153
- nextSteps.push(`There are ${queuedChanges.length} queued change(s) waiting behind the active one. Use "ospec run ${runReport.currentRun ? 'step' : 'start'} ${projectPath}" when you want explicit queue progression.`);
167
+ nextSteps.push(`There are ${queuedChanges.length} queued change(s) waiting behind the active one. Use "${formatCommand('run', runReport.currentRun ? 'step' : 'start', projectPath)}" when you want explicit queue progression.`);
154
168
  }
155
169
  return nextSteps;
156
170
  }
@@ -98,48 +98,12 @@ class VerifyCommand extends BaseCommand_1.BaseCommand {
98
98
  });
99
99
  }
100
100
  if (tasksExists) {
101
- const tasksContent = await services_1.services.fileService.readFile(tasksPath);
102
- const tasks = (0, gray_matter_1.default)(tasksContent);
103
- const optionalSteps = Array.isArray(tasks.data.optional_steps)
104
- ? tasks.data.optional_steps
105
- : [];
106
- const missing = activatedSteps.filter(step => !optionalSteps.includes(step));
107
- checks.push({
108
- name: 'tasks.optional_steps',
109
- status: missing.length === 0 ? 'pass' : 'fail',
110
- message: missing.length === 0
111
- ? 'All activated optional steps are present in tasks.md'
112
- : `Missing optional steps in tasks.md: ${missing.join(', ')}`,
113
- });
114
- checks.push({
115
- name: 'tasks checklist',
116
- status: /- \[ \]/.test(tasksContent) ? 'warn' : 'pass',
117
- message: /- \[ \]/.test(tasksContent)
118
- ? 'tasks.md still has unchecked items'
119
- : 'tasks.md checklist is complete',
120
- });
101
+ const tasksAnalysis = await services_1.services.projectService.analyzeChecklistDocument(tasksPath, 'tasks.md', activatedSteps);
102
+ checks.push(...tasksAnalysis.checks);
121
103
  }
122
104
  if (verificationExists) {
123
- const verificationContent = await services_1.services.fileService.readFile(verificationPath);
124
- const verification = (0, gray_matter_1.default)(verificationContent);
125
- const optionalSteps = Array.isArray(verification.data.optional_steps)
126
- ? verification.data.optional_steps
127
- : [];
128
- const missing = activatedSteps.filter(step => !optionalSteps.includes(step));
129
- checks.push({
130
- name: 'verification.optional_steps',
131
- status: missing.length === 0 ? 'pass' : 'fail',
132
- message: missing.length === 0
133
- ? 'All activated optional steps are present in verification.md'
134
- : `Missing optional steps in verification.md: ${missing.join(', ')}`,
135
- });
136
- checks.push({
137
- name: 'verification checklist',
138
- status: /- \[ \]/.test(verificationContent) ? 'warn' : 'pass',
139
- message: /- \[ \]/.test(verificationContent)
140
- ? 'verification.md still has unchecked items'
141
- : 'verification.md checklist is complete',
142
- });
105
+ const verificationAnalysis = await services_1.services.projectService.analyzeVerificationDocument(verificationPath, activatedSteps);
106
+ checks.push(...verificationAnalysis.checks);
143
107
  }
144
108
  if (activatedSteps.includes('stitch_design_review')) {
145
109
  const approvalPath = path.join(targetPath, 'artifacts', 'stitch', 'approval.json');
@@ -126,6 +126,7 @@ export declare class ProjectService {
126
126
  archivePath: string;
127
127
  change: ActiveChangeStatusItem;
128
128
  }>;
129
+ rebaseMovedChangeMarkdownLinks(previousChangePath: string, nextChangePath: string): Promise<void>;
129
130
  getFeatureProjectContext(rootDir: string, affects?: string[]): Promise<FeatureProjectContext>;
130
131
  getDocsStatus(rootDir: string): Promise<DocsStatus>;
131
132
  getSkillsStatus(rootDir: string): Promise<SkillsStatus>;