@clawplays/ospec-cli
Advanced tools
@@ -49,2 +49,3 @@ --- | ||
| - الإغلاق تلقائي عند الجاهزية: عندما ينجح `ospec verify [changes/active/<change>]` ولا توجد required pending decision أو بوابة إضافة (plugin gate) حاجبة، شغّل `ospec finalize [changes/active/<change>]` بنفسك——لا تتوقف بعد نجاح `ospec verify` أو `ospec archive ... --check` (الـ `--check` معاينة فقط) ولا تنتظر طلب المستخدم. لا توقف الإغلاق إلا حين تحتاج بوابة فعلاً إلى إنسان: required decision غير مُجابة، أو بوابة إضافة حاجبة غير معتمدة (مثل Stitch أو Checkpoint)، أو blockers حقيقية يبلّغ عنها verify أو archive، أو طلب صريح من المستخدم للمعاينة أو الموافقة قبل الأرشفة | ||
| - الأرشفة القسرية استثناء بتفويض صريح من المستخدم وليست مسارا احتياطيا تلقائيا. أبلغ أولا عن كل البوابات الفاشلة وعناصر `NOT_VERIFIED` وتأكد من عدم وجود Loop action معلق، ثم شغّل `ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <exact-change-name> --reason "<accepted risk>"`. لا تحول الفشل إلى نجاح؛ يبقى الأرشيف incomplete وaccepted-risk. | ||
| - بوابات القرار وخيارات brainstorm ملك للمستخدم: **لا تختر الخيار الموصى به تلقائياً ولا تحلّ بوابة بنفسك**——اعرض كل بوابة عبر سلّم القدرات (واجهة سؤال أصلية ← واجهة خطة/موافقة ← نص محادثة عادي) وانتظر اختيار المستخدم الفعلي؛ البوابات الإلزامية تحجب التنفيذ والإرسال حتى يجيب المستخدم، و`recommended` مجرد تلميح يُعرض على المستخدم | ||
@@ -51,0 +52,0 @@ - اكتب كل وثيقة change وbrainstorm تنشئها بلغة وثائق المشروع (`documentLanguage` في `.skillrc` / إرشادات `for-ai/` المُدارة)؛ لا تخلط العربية والإنجليزية داخل change واحد |
@@ -54,2 +54,3 @@ --- | ||
| - الإغلاق تلقائي عند الجاهزية: عندما ينجح `ospec verify [changes/active/<change>]` ولا توجد required pending decision أو بوابة إضافة (plugin gate) حاجبة، شغّل `ospec finalize [changes/active/<change>]` بنفسك——لا تتوقف بعد نجاح `ospec verify` أو `ospec archive ... --check` (الـ `--check` معاينة فقط) ولا تنتظر طلب المستخدم. لا توقف الإغلاق إلا حين تحتاج بوابة فعلاً إلى إنسان: required decision غير مُجابة، أو بوابة إضافة حاجبة غير معتمدة (مثل Stitch أو Checkpoint)، أو blockers حقيقية يبلّغ عنها verify أو archive، أو طلب صريح من المستخدم للمعاينة أو الموافقة قبل الأرشفة | ||
| - الأرشفة القسرية استثناء بتفويض صريح من المستخدم وليست مسارا احتياطيا تلقائيا. أبلغ أولا عن كل البوابات الفاشلة وعناصر `NOT_VERIFIED` وتأكد من عدم وجود Loop action معلق، ثم شغّل `ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <exact-change-name> --reason "<accepted risk>"`. لا تحول الفشل إلى نجاح؛ يبقى الأرشيف incomplete وaccepted-risk. | ||
| - بوابات القرار وخيارات brainstorm ملك للمستخدم: **لا تختر الخيار الموصى به تلقائياً ولا تحلّ بوابة بنفسك**——اعرض كل بوابة عبر سلّم القدرات (واجهة سؤال أصلية ← واجهة خطة/موافقة ← نص محادثة عادي) وانتظر اختيار المستخدم الفعلي؛ البوابات الإلزامية تحجب التنفيذ والإرسال حتى يجيب المستخدم، و`recommended` مجرد تلميح يُعرض على المستخدم | ||
@@ -56,0 +57,0 @@ - اكتب كل وثيقة change وbrainstorm تنشئها بلغة وثائق المشروع (`documentLanguage` في `.skillrc` / إرشادات `for-ai/` المُدارة)؛ لا تخلط العربية والإنجليزية داخل change واحد |
@@ -51,2 +51,3 @@ --- | ||
| - Closeout is automatic when ready: once `ospec verify [changes/active/<change>]` passes and no required user decision or blocking plugin gate is pending, run `ospec finalize [changes/active/<change>]` yourself — do not stop after a passing `ospec verify` or `ospec archive ... --check` (`--check` is a preview only) and do not wait for the user to ask. Only pause closeout when a gate genuinely needs a human: a pending required user decision, an unapproved blocking plugin gate (for example Stitch or Checkpoint), real blockers reported by verify or archive, or an explicit user request to preview or approve before archiving | ||
| - Force archive is an explicit user exception, never an automatic fallback. First report the failing gates and every `NOT_VERIFIED` item and confirm there is no pending Loop action. Then use `ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <exact-change-name> --reason "<accepted risk>"`. Preserve failures; the archive remains incomplete and accepted-risk. | ||
| - Decision gates and brainstorm options belong to the user: never auto-select the recommended option or resolve a gate yourself — present every gate with the capability ladder (a native question UI, else a plan/approval UI, else plain chat text) and wait for the user's actual choice; required gates block implementation and dispatch until the user answers, and `recommended` is only a hint to show the user | ||
@@ -53,0 +54,0 @@ - Write every change document and brainstorm you author in the project's document language (`.skillrc` `documentLanguage` / managed `for-ai/` guidance); do not mix Chinese and English within a change |
@@ -56,2 +56,3 @@ --- | ||
| - Closeout is automatic when ready: once `ospec verify [changes/active/<change>]` passes and no required user decision or blocking plugin gate is pending, run `ospec finalize [changes/active/<change>]` yourself — do not stop after a passing `ospec verify` or `ospec archive ... --check` (`--check` is a preview only) and do not wait for the user to ask. Only pause closeout when a gate genuinely needs a human: a pending required user decision, an unapproved blocking plugin gate (for example Stitch or Checkpoint), real blockers reported by verify or archive, or an explicit user request to preview or approve before archiving | ||
| - Force archive is an explicit user exception, never an automatic fallback. First report the failing gates and every `NOT_VERIFIED` item and confirm there is no pending Loop action. Then use `ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <exact-change-name> --reason "<accepted risk>"`. Preserve failures; the archive remains incomplete and accepted-risk. | ||
| - Decision gates and brainstorm options belong to the user: never auto-select the recommended option or resolve a gate yourself — present every gate with the capability ladder (a native question UI, else a plan/approval UI, else plain chat text) and wait for the user's actual choice; required gates block implementation and dispatch until the user answers, and `recommended` is only a hint to show the user | ||
@@ -58,0 +59,0 @@ - Write every change document and brainstorm you author in the project's document language (`.skillrc` `documentLanguage` / managed `for-ai/` guidance); do not mix Chinese and English within a change |
@@ -49,2 +49,3 @@ --- | ||
| - 準備ができたら closeout は自動:`ospec verify [changes/active/<change>]` が通過し、required pending decision や blocking なプラグイン gate がない場合は、自分で `ospec finalize [changes/active/<change>]` を実行する——通過した `ospec verify` や `ospec archive ... --check`(`--check` は preview のみ)で止まらず、ユーザーの依頼を待たない。closeout を止めるのは gate が本当に人を必要とするときだけ:未回答の required decision、未承認の blocking プラグイン gate(例:Stitch や Checkpoint)、verify や archive が報告する実際の blocker、またはユーザーが archive 前に preview/承認を明示的に求めた場合 | ||
| - 強制 archive はユーザーが明示した例外であり、自動 fallback ではない。失敗 gate とすべての `NOT_VERIFIED` 項目を報告し、pending Loop action がないことを確認してから、`ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <正確な-change-名> --reason "<受容するリスク>"` を実行する。失敗を pass に書き換えず、archive は incomplete / accepted-risk のままにする。 | ||
| - 決定ゲートと brainstorm の選択肢はユーザーのもの:**推奨オプションを自動選択したり、自分でゲートを resolve したりしない**——能力ラダー(ネイティブ質問 UI → plan/承認 UI → 素のチャットテキスト)で各ゲートをユーザーに提示し、ユーザーの実際の選択を待つ。required ゲートはユーザーが答えるまで実装とディスパッチをブロックし、`recommended` はユーザーに見せるヒントにすぎない | ||
@@ -51,0 +52,0 @@ - あなたが作成するすべての change ドキュメントと brainstorm は、プロジェクトのドキュメント言語(`.skillrc` の `documentLanguage` / 管理対象の `for-ai/` ガイダンス)で書く。1 つの change 内で中国語と英語を混在させない |
@@ -54,2 +54,3 @@ --- | ||
| - 準備ができたら closeout は自動:`ospec verify [changes/active/<change>]` が通過し、required pending decision や blocking なプラグイン gate がない場合は、自分で `ospec finalize [changes/active/<change>]` を実行する——通過した `ospec verify` や `ospec archive ... --check`(`--check` は preview のみ)で止まらず、ユーザーの依頼を待たない。closeout を止めるのは gate が本当に人を必要とするときだけ:未回答の required decision、未承認の blocking プラグイン gate(例:Stitch や Checkpoint)、verify や archive が報告する実際の blocker、またはユーザーが archive 前に preview/承認を明示的に求めた場合 | ||
| - 強制 archive はユーザーが明示した例外であり、自動 fallback ではない。失敗 gate とすべての `NOT_VERIFIED` 項目を報告し、pending Loop action がないことを確認してから、`ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <正確な-change-名> --reason "<受容するリスク>"` を実行する。失敗を pass に書き換えず、archive は incomplete / accepted-risk のままにする。 | ||
| - 決定ゲートと brainstorm の選択肢はユーザーのもの:**推奨オプションを自動選択したり、自分でゲートを resolve したりしない**——能力ラダー(ネイティブ質問 UI → plan/承認 UI → 素のチャットテキスト)で各ゲートをユーザーに提示し、ユーザーの実際の選択を待つ。required ゲートはユーザーが答えるまで実装とディスパッチをブロックし、`recommended` はユーザーに見せるヒントにすぎない | ||
@@ -56,0 +57,0 @@ - あなたが作成するすべての change ドキュメントと brainstorm は、プロジェクトのドキュメント言語(`.skillrc` の `documentLanguage` / 管理対象の `for-ai/` ガイダンス)で書く。1 つの change 内で中国語と英語を混在させない |
@@ -51,2 +51,3 @@ --- | ||
| - 准备好就自动收尾:当 `ospec verify [changes/active/<change>]` 通过、且没有 required pending decision 或 blocking 插件 gate 时,自己运行 `ospec finalize [changes/active/<change>]`——不要停在通过的 `ospec verify` 或 `ospec archive ... --check`(`--check` 只是预览),也不要等用户来要求。只有当某个 gate 确实需要人工时才暂停收尾:尚未答复的 required decision、未批准的 blocking 插件 gate(如 Stitch 或 Checkpoint)、verify 或 archive 报出的真实 blocker,或用户明确要求先预览或批准再归档 | ||
| - 强制归档只能是用户明确授权的例外,不能作为自动兜底。先报告全部失败门禁和 `NOT_VERIFIED` 项,并确认没有 pending Loop action;再运行 `ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <精确-change-名称> --reason "<已接受风险>"`。不得把失败改写为通过;归档必须保持 incomplete 和 accepted-risk 标记。 | ||
| - 决策门和 brainstorm 选项属于用户:**绝不要自动选"推荐项"、也不要自己 resolve 决策门**——用能力阶梯(原生问答 UI → Plan/审批 UI → 纯聊天文字)把每个门呈现给用户,等用户真正回答;required 门在用户回答前会阻塞实现与派发,`recommended` 只是给用户看的提示 | ||
@@ -53,0 +54,0 @@ - 你产出的每个 change 文档和 brainstorm 都要用项目的文档语言(`.skillrc` 的 `documentLanguage` / 托管 `for-ai/` 指引)书写;同一个 change 内不要中英文混用 |
@@ -56,2 +56,3 @@ --- | ||
| - 准备好就自动收尾:当 `ospec verify [changes/active/<change>]` 通过、且没有 required pending decision 或 blocking 插件 gate 时,自己运行 `ospec finalize [changes/active/<change>]`——不要停在通过的 `ospec verify` 或 `ospec archive ... --check`(`--check` 只是预览),也不要等用户来要求。只有当某个 gate 确实需要人工时才暂停收尾:尚未答复的 required decision、未批准的 blocking 插件 gate(如 Stitch 或 Checkpoint)、verify 或 archive 报出的真实 blocker,或用户明确要求先预览或批准再归档 | ||
| - 强制归档只能是用户明确授权的例外,不能作为自动兜底。先报告全部失败门禁和 `NOT_VERIFIED` 项,并确认没有 pending Loop action;再运行 `ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <精确-change-名称> --reason "<已接受风险>"`。不得把失败改写为通过;归档必须保持 incomplete 和 accepted-risk 标记。 | ||
| - 决策门和 brainstorm 选项属于用户:**绝不要自动选"推荐项"、也不要自己 resolve 决策门**——用能力阶梯(原生问答 UI → Plan/审批 UI → 纯聊天文字)把每个门呈现给用户,等用户真正回答;required 门在用户回答前会阻塞实现与派发,`recommended` 只是给用户看的提示 | ||
@@ -58,0 +59,0 @@ - 你产出的每个 change 文档和 brainstorm 都要用项目的文档语言(`.skillrc` 的 `documentLanguage` / 托管 `for-ai/` 指引)书写;同一个 change 内不要中英文混用 |
@@ -62,2 +62,3 @@ --- | ||
| 12. Closeout is automatic when ready: once implementation, `verification.md`, documentation policy, plugin gates, and `review.md` are aligned and `ospec verify [changes/active/<change>]` passes, run `ospec finalize [changes/active/<change>]` yourself. Do not stop at `ospec archive ... --check` (it is a preview only) and do not wait for the user to ask before archiving. **Closeout uses `direct-closeout` (archive locally, no PR) and `manual` merge as defaults — do NOT ask the user about PR, merge, branch, or worktree strategy; uncommitted change/OSpec files in the working tree are normal and do not block archive. Only open a PR if the user explicitly asked.** Only pause closeout when a gate genuinely needs a human: a pending required user decision, an unapproved blocking plugin gate (e.g. Checkpoint), real blockers reported by verify or archive, or an explicit user request to preview or approve before archiving. | ||
| 13. Force archive is never automatic. Use it only after the user explicitly accepts incomplete work: report the failed gates, confirm there is no pending Loop action, then run `ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <exact-change-name> --reason "<accepted risk>"`. Preserve failed and `NOT_VERIFIED` evidence; the archive is marked incomplete and must not be presented as completed behavior. | ||
@@ -74,2 +75,3 @@ ## Commands | ||
| ospec finalize [changes/active/<change>] # run automatically once verify passes and no human gate is pending | ||
| ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <exact-change-name> --reason "<accepted risk>" # explicit user exception only | ||
| ``` | ||
@@ -76,0 +78,0 @@ |
@@ -89,5 +89,6 @@ --- | ||
| 11. Use `ospec execute workspace`, `dispatch`, `launch`, `complete`, `review`, `feedback`, `repair`, `sync`, `tdd`, `debug`, and `verify` as needed for the full workflow. Run `ospec loop run <goal-path> --once --json`, dispatch every emitted packet through `runtimeAdapter.selected.nativeSubagent`, record completion/review evidence as each child finishes, and tick again without waiting for another user prompt. Never start Orca, Codex, Claude, or another agent CLI as a fallback. Model profiles are logical and resolve through `.skillrc.workflow.model_profiles`; `complete --usage-file` may record provider usage. Require reviewers to write Markdown plus sibling structured `*.findings.json`. If final review is `NEEDS_CHANGES`, create one grouped repair task instead of one worker per finding. | ||
| 12. Do not archive while task graph status, task-level reviews, final reviews, worker status, required user decisions, document reviews, or verification evidence are incomplete. | ||
| 12. Do not archive while task graph status, task-level reviews, final reviews, worker status, required user decisions, document reviews, or verification evidence are incomplete during normal closeout. | ||
| 13. Use `ospec execute finish` before finalize when the goal used task graph execution or worktree planning. | ||
| 14. Use `ospec finalize [changes/active/<goal>]` as the normal closeout path. Closeout is automatic when ready: once the goal is complete and `ospec verify` passes with no required user decision or blocking gate pending, run `ospec finalize` yourself — do not stop at `ospec archive ... --check` (preview only) or wait for the user to ask. **`ospec execute finish` strategy prompts (PR / merge / branch / worktree) are optional with safe defaults (`direct-closeout` + `manual` merge) — do NOT ask the user about them; uncommitted change/OSpec files are normal and do not block archive. Only open a PR if the user explicitly asked.** Only pause for a genuine human gate: a pending required decision, an unapproved blocking plugin gate (e.g. Checkpoint), real verify/archive blockers, or an explicit user request to preview or approve first. | ||
| 15. Force archive is never automatic. Only after the user explicitly asks to accept an incomplete Goal, report every failing gate and `NOT_VERIFIED` item, double-check that no Loop action or child is pending, and run `ospec finalize [changes/active/<goal>] --force-archive --confirm-force-archive <exact-goal-name> --reason "<accepted risk>"`. Do not rewrite failed evidence as passed. The resulting archive remains `forced`, `incomplete`, and `accepted-risk`, not completed behavior. | ||
@@ -152,2 +153,3 @@ ## Commands | ||
| ospec finalize [changes/active/<goal>] | ||
| ospec finalize [changes/active/<goal>] --force-archive --confirm-force-archive <exact-goal-name> --reason "<accepted risk>" | ||
| ``` | ||
@@ -154,0 +156,0 @@ |
+77
-3
@@ -65,3 +65,3 @@ #!/usr/bin/env node | ||
| const services_1 = require("./services"); | ||
| const CLI_VERSION = '1.8.20'; | ||
| const CLI_VERSION = '1.8.21'; | ||
| function showInitUsage() { | ||
@@ -156,2 +156,74 @@ console.log('Usage: ospec init [root-dir] [--summary "..."] [--tech-stack node,react] [--architecture "..."] [--document-language en-US|zh-CN|ja-JP|ar]'); | ||
| } | ||
| function showFinalizeUsage() { | ||
| console.log('Usage: ospec finalize [changes/active/<change>] [--force-archive --confirm-force-archive <exact-change-name> (--reason "..." | --reason-file <path>)]'); | ||
| } | ||
| function parseFinalizeCommandArgs(commandArgs) { | ||
| let featurePath; | ||
| const options = {}; | ||
| const readValue = (index, flag) => { | ||
| const value = commandArgs[index + 1]; | ||
| if (!value || value.startsWith('--')) { | ||
| console.error(`Error: ${flag} requires a value`); | ||
| showFinalizeUsage(); | ||
| process.exit(1); | ||
| } | ||
| return value; | ||
| }; | ||
| for (let index = 0; index < commandArgs.length; index += 1) { | ||
| const arg = commandArgs[index]; | ||
| if (arg === '--help' || arg === '-h' || arg === 'help') { | ||
| showFinalizeUsage(); | ||
| process.exit(0); | ||
| } | ||
| if (arg === '--force-archive') { | ||
| options.forceArchive = true; | ||
| continue; | ||
| } | ||
| if (arg === '--confirm-force-archive') { | ||
| options.confirmForceArchive = readValue(index, arg); | ||
| index += 1; | ||
| continue; | ||
| } | ||
| if (arg.startsWith('--confirm-force-archive=')) { | ||
| options.confirmForceArchive = arg.slice('--confirm-force-archive='.length); | ||
| continue; | ||
| } | ||
| if (arg === '--reason') { | ||
| options.reason = readValue(index, arg); | ||
| index += 1; | ||
| continue; | ||
| } | ||
| if (arg.startsWith('--reason=')) { | ||
| options.reason = arg.slice('--reason='.length); | ||
| continue; | ||
| } | ||
| if (arg === '--reason-file') { | ||
| options.reasonFile = readValue(index, arg); | ||
| index += 1; | ||
| continue; | ||
| } | ||
| if (arg.startsWith('--reason-file=')) { | ||
| options.reasonFile = arg.slice('--reason-file='.length); | ||
| continue; | ||
| } | ||
| if (arg.startsWith('--')) { | ||
| console.error(`Error: unknown finalize option "${arg}"`); | ||
| showFinalizeUsage(); | ||
| process.exit(1); | ||
| } | ||
| if (featurePath === undefined) { | ||
| featurePath = arg; | ||
| continue; | ||
| } | ||
| console.error(`Error: unexpected finalize argument "${arg}"`); | ||
| showFinalizeUsage(); | ||
| process.exit(1); | ||
| } | ||
| if (options.reason !== undefined && options.reasonFile !== undefined) { | ||
| console.error('Error: use either --reason or --reason-file, not both'); | ||
| showFinalizeUsage(); | ||
| process.exit(1); | ||
| } | ||
| return { featurePath, options }; | ||
| } | ||
| function getNewLikeUsage(commandName) { | ||
@@ -384,3 +456,4 @@ return commandName === 'goal' | ||
| const finalizeCmd = new FinalizeCommand_1.FinalizeCommand(); | ||
| await finalizeCmd.execute(commandArgs[0]); | ||
| const { featurePath, options } = parseFinalizeCommandArgs(commandArgs); | ||
| await finalizeCmd.execute(featurePath, options); | ||
| break; | ||
@@ -517,3 +590,3 @@ } | ||
| session [path] Write a project session brief and safe next command | ||
| finalize [path] Verify a completed change and archive it before commit | ||
| finalize [path] Verify and archive, or force-archive with explicit double confirmation | ||
| batch <action> [path] Batch operations (export, stats) | ||
@@ -553,2 +626,3 @@ changes [action] [path] Active change summaries (status) | ||
| ospec finalize ./changes/active/onboarding-flow | ||
| ospec finalize ./changes/active/onboarding-flow --force-archive --confirm-force-archive onboarding-flow --reason "Accepted incomplete verification risk" | ||
| ospec status | ||
@@ -555,0 +629,0 @@ ospec session |
@@ -0,3 +1,9 @@ | ||
| export interface FinalizeCommandOptions { | ||
| forceArchive?: boolean; | ||
| confirmForceArchive?: string; | ||
| reason?: string; | ||
| reasonFile?: string; | ||
| } | ||
| export declare class FinalizeCommand { | ||
| execute(featurePath?: string): Promise<void>; | ||
| execute(featurePath?: string, options?: FinalizeCommandOptions): Promise<void>; | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.FinalizeCommand = void 0; | ||
| const fs = require("fs").promises; | ||
| const path = require("path"); | ||
@@ -8,8 +9,18 @@ const services_1 = require("../services"); | ||
| class FinalizeCommand extends BaseCommand_1.BaseCommand { | ||
| async execute(featurePath) { | ||
| async execute(featurePath, options = {}) { | ||
| try { | ||
| const targetPath = featurePath || process.cwd(); | ||
| if (options.reason !== undefined && options.reasonFile !== undefined) { | ||
| throw new Error('Use either --reason or --reason-file, not both.'); | ||
| } | ||
| const reason = options.reasonFile !== undefined | ||
| ? await fs.readFile(path.resolve(process.cwd(), options.reasonFile), 'utf8') | ||
| : options.reason; | ||
| this.info(`Finalizing change at ${targetPath}`); | ||
| const result = await services_1.services.projectService.finalizeChange(path.resolve(targetPath)); | ||
| this.success(`Change finalized: ${result.archivePath}`); | ||
| const result = await services_1.services.projectService.finalizeChange(path.resolve(targetPath), { | ||
| forceArchive: options.forceArchive, | ||
| confirmForceArchive: options.confirmForceArchive, | ||
| reason, | ||
| }); | ||
| this.success(`${options.forceArchive ? 'Change force-archived' : 'Change finalized'}: ${result.archivePath}`); | ||
| } | ||
@@ -16,0 +27,0 @@ catch (error) { |
@@ -13,3 +13,3 @@ /** | ||
| export { ArchiveCommand } from './ArchiveCommand'; | ||
| export { FinalizeCommand } from './FinalizeCommand'; | ||
| export { FinalizeCommand, type FinalizeCommandOptions } from './FinalizeCommand'; | ||
| export { StatusCommand } from './StatusCommand'; | ||
@@ -16,0 +16,0 @@ export { BatchCommand } from './BatchCommand'; |
@@ -22,2 +22,3 @@ export declare const STATE_TRANSITIONS: Record<string, string[]>; | ||
| VERIFICATION: string; | ||
| FORCE_ARCHIVE_RECORD: string; | ||
| STATE: string; | ||
@@ -24,0 +25,0 @@ REVIEW: string; |
@@ -46,2 +46,3 @@ "use strict"; | ||
| VERIFICATION: 'verification.md', | ||
| FORCE_ARCHIVE_RECORD: 'force-archive.json', | ||
| STATE: 'state.json', | ||
@@ -48,0 +49,0 @@ REVIEW: 'review.md', |
@@ -210,2 +210,8 @@ export type ProjectMode = 'lite' | 'standard' | 'full'; | ||
| workflow_profile?: string; | ||
| disposition?: 'completed' | 'forced'; | ||
| completion_status?: 'completed' | 'incomplete'; | ||
| accepted_risk?: boolean; | ||
| force_archive_reason?: string; | ||
| failing_checks?: string[]; | ||
| archived_at?: string; | ||
| } | ||
@@ -212,0 +218,0 @@ export interface SkillIndex { |
@@ -15,3 +15,3 @@ /** | ||
| export { ProjectScaffoldCommandService, createProjectScaffoldCommandService, } from './ProjectScaffoldCommandService'; | ||
| export { ProjectService, createProjectService } from './ProjectService'; | ||
| export { ProjectService, createProjectService, type FinalizeChangeOptions } from './ProjectService'; | ||
| export { QueueService, createQueueService } from './QueueService'; | ||
@@ -18,0 +18,0 @@ export { RunService, createRunService } from './RunService'; |
@@ -22,2 +22,3 @@ "use strict"; | ||
| 'artifacts/reviews/final-review.md', | ||
| 'artifacts/agents/force-archive.json', | ||
| ]; | ||
@@ -428,2 +429,15 @@ async function pathExists(targetPath) { | ||
| const archive = path_1.default.relative(rootDir, archiveDir).replace(/\\/g, '/'); | ||
| const disposition = state.archive_disposition === 'forced' ? 'forced' : undefined; | ||
| let forceArchiveRecord = null; | ||
| if (disposition === 'forced') { | ||
| const forceRecordPath = path_1.default.join(archiveDir, 'artifacts', 'agents', constants_1.FILE_NAMES.FORCE_ARCHIVE_RECORD); | ||
| if (await pathExists(forceRecordPath)) { | ||
| try { | ||
| forceArchiveRecord = await readJson(forceRecordPath); | ||
| } | ||
| catch { | ||
| forceArchiveRecord = null; | ||
| } | ||
| } | ||
| } | ||
| const expectedKnowledgeDocument = this.getKnowledgeDocumentRelativePath(archive); | ||
@@ -439,7 +453,9 @@ const knowledgeDocument = expectedKnowledgeDocument | ||
| archive, | ||
| completed_at: typeof state.completed_at === 'string' | ||
| ? state.completed_at | ||
| : typeof state.last_updated === 'string' | ||
| ? state.last_updated | ||
| : null, | ||
| completed_at: disposition === 'forced' | ||
| ? null | ||
| : typeof state.completed_at === 'string' | ||
| ? state.completed_at | ||
| : typeof state.last_updated === 'string' | ||
| ? state.last_updated | ||
| : null, | ||
| documents, | ||
@@ -451,2 +467,17 @@ project_documents: [...projectDocuments].sort(), | ||
| workflow_profile: typeof state.workflow_profile_id === 'string' ? state.workflow_profile_id : undefined, | ||
| ...(disposition === 'forced' ? { | ||
| disposition, | ||
| completion_status: 'incomplete', | ||
| accepted_risk: true, | ||
| force_archive_reason: typeof forceArchiveRecord?.reason === 'string' | ||
| ? forceArchiveRecord.reason | ||
| : '', | ||
| failing_checks: Array.from(new Set([ | ||
| ...(Array.isArray(forceArchiveRecord?.failingChecks) ? forceArchiveRecord.failingChecks : []) | ||
| .map((check) => String(check?.name || '').trim()), | ||
| ...(Array.isArray(forceArchiveRecord?.progressIssues) ? forceArchiveRecord.progressIssues : []) | ||
| .map((issue) => `goal.progress: ${String(issue || '').trim()}`), | ||
| ].filter(Boolean))), | ||
| archived_at: typeof state.archived_at === 'string' ? state.archived_at : undefined, | ||
| } : {}), | ||
| }; | ||
@@ -484,2 +515,3 @@ } | ||
| const archiveLink = path_1.default.relative(path_1.default.dirname(resolvedTarget), archiveAbsolute).replace(/\\/g, '/'); | ||
| const forced = change.disposition === 'forced'; | ||
| const lines = [ | ||
@@ -489,3 +521,5 @@ '---', | ||
| `title: ${JSON.stringify(change.feature)}`, | ||
| 'tags: [project, feature, completed, archive, ai-index]', | ||
| forced | ||
| ? 'tags: [project, feature, archive, incomplete, accepted-risk, ai-index]' | ||
| : 'tags: [project, feature, completed, archive, ai-index]', | ||
| `features: [${JSON.stringify(change.feature)}]`, | ||
@@ -495,2 +529,8 @@ `archive: ${JSON.stringify(change.archive)}`, | ||
| `completed_at: ${JSON.stringify(change.completed_at || '')}`, | ||
| ...(forced ? [ | ||
| 'disposition: forced', | ||
| 'completion_status: incomplete', | ||
| 'accepted_risk: true', | ||
| `force_archive_reason: ${JSON.stringify(change.force_archive_reason || '')}`, | ||
| ] : []), | ||
| 'generated: true', | ||
@@ -502,4 +542,14 @@ 'generator: ospec-archive-knowledge', | ||
| '', | ||
| `> ${copy.guidance}`, | ||
| forced ? `> **${copy.forceWarning}**` : `> ${copy.guidance}`, | ||
| '', | ||
| ...(forced ? [ | ||
| `## ${copy.archiveDisposition}`, | ||
| '', | ||
| `- ${copy.disposition}: forced`, | ||
| `- ${copy.completionStatus}: incomplete`, | ||
| `- ${copy.acceptedRisk}: true`, | ||
| `- ${copy.forceReason}: ${change.force_archive_reason || copy.notRecorded}`, | ||
| `- ${copy.failingGates}: ${(change.failing_checks || []).length > 0 ? (change.failing_checks || []).join(', ') : copy.none.replace(/^- /, '')}`, | ||
| '', | ||
| ] : []), | ||
| `## ${copy.summary}`, | ||
@@ -631,2 +681,9 @@ '', | ||
| notRecorded: '未记录摘要,请打开归档 proposal 查看。', | ||
| forceWarning: '此 change 已在未完成状态下被强制归档,不能视为已验证完成。', | ||
| archiveDisposition: '强制归档状态', | ||
| disposition: '归档方式', | ||
| completionStatus: '完成状态', | ||
| acceptedRisk: '已接受风险', | ||
| forceReason: '强制归档原因', | ||
| failingGates: '未通过门禁', | ||
| }; | ||
@@ -645,2 +702,9 @@ if (documentLanguage === 'ja-JP') | ||
| notRecorded: '概要は記録されていません。archive の proposal を開いてください。', | ||
| forceWarning: 'この change は未完了のまま強制 archive されており、検証済み完了として扱えません。', | ||
| archiveDisposition: '強制 archive 状態', | ||
| disposition: 'archive 方法', | ||
| completionStatus: '完了状態', | ||
| acceptedRisk: '受容済みリスク', | ||
| forceReason: '強制 archive 理由', | ||
| failingGates: '未通過 gate', | ||
| }; | ||
@@ -659,2 +723,9 @@ if (documentLanguage === 'ar') | ||
| notRecorded: 'لم يسجل ملخص؛ افتح proposal المؤرشف.', | ||
| forceWarning: 'تمت أرشفة هذا التغيير قسريا وهو غير مكتمل، ولم يتم التحقق من اكتماله.', | ||
| archiveDisposition: 'حالة الأرشفة القسرية', | ||
| disposition: 'طريقة الأرشفة', | ||
| completionStatus: 'حالة الاكتمال', | ||
| acceptedRisk: 'المخاطر المقبولة', | ||
| forceReason: 'سبب الأرشفة القسرية', | ||
| failingGates: 'البوابات غير المجتازة', | ||
| }; | ||
@@ -672,2 +743,9 @@ return { | ||
| notRecorded: 'No summary was recorded; open the archived proposal.', | ||
| forceWarning: 'This change was force-archived incomplete. It was not verified as complete.', | ||
| archiveDisposition: 'Force Archive Status', | ||
| disposition: 'Disposition', | ||
| completionStatus: 'Completion status', | ||
| acceptedRisk: 'Accepted risk', | ||
| forceReason: 'Force archive reason', | ||
| failingGates: 'Failing gates', | ||
| }; | ||
@@ -700,2 +778,7 @@ } | ||
| lines.push(`## ${change.feature}`, ''); | ||
| if (change.disposition === 'forced') { | ||
| lines.push(`- ${copy.archiveStatus}: FORCED / INCOMPLETE / ACCEPTED RISK`); | ||
| lines.push(`- ${copy.forceReason}: ${change.force_archive_reason || copy.notRecorded}`); | ||
| lines.push(`- ${copy.failingGates}: ${(change.failing_checks || []).length > 0 ? (change.failing_checks || []).join(', ') : copy.none}`); | ||
| } | ||
| if (change.summary) | ||
@@ -731,3 +814,3 @@ lines.push(`- ${copy.summary}: ${change.summary}`); | ||
| title: '项目功能索引', | ||
| guidance: '由 OSpec 自动生成。使用本文件定位已完成功能,并只打开当前任务需要的归档证据或长期项目文档。', | ||
| guidance: '由 OSpec 自动生成。使用本文件定位归档记录;强制归档的未完成项会明确标记,不能视为已完成功能。', | ||
| empty: '暂无已归档 change。', | ||
@@ -740,2 +823,7 @@ summary: '摘要', | ||
| knowledgeDocument: 'change 功能文档', | ||
| archiveStatus: '归档状态', | ||
| forceReason: '强制归档原因', | ||
| failingGates: '未通过门禁', | ||
| notRecorded: '未记录', | ||
| none: '无', | ||
| }; | ||
@@ -746,3 +834,3 @@ } | ||
| title: 'プロジェクト機能索引', | ||
| guidance: 'OSpec により自動生成されます。完了済み機能を特定し、現在のタスクに必要な archive evidence または永続 project document だけを開いてください。', | ||
| guidance: 'OSpec により自動生成されます。archive 記録を特定し、強制 archive された未完了項目を完了済み機能として扱わないでください。', | ||
| empty: 'archive 済みの change はまだありません。', | ||
@@ -755,2 +843,7 @@ summary: '概要', | ||
| knowledgeDocument: 'change 機能文書', | ||
| archiveStatus: 'archive 状態', | ||
| forceReason: '強制 archive 理由', | ||
| failingGates: '未通過 gate', | ||
| notRecorded: '記録なし', | ||
| none: 'なし', | ||
| }; | ||
@@ -761,3 +854,3 @@ } | ||
| title: 'فهرس ميزات المشروع', | ||
| guidance: 'يُنشأ تلقائياً بواسطة OSpec. استخدمه لتحديد السلوك المكتمل، وافتح فقط دليل archive أو وثيقة المشروع الدائمة اللازمة للمهمة الحالية.', | ||
| guidance: 'يُنشأ تلقائياً بواسطة OSpec لتحديد سجلات الأرشيف؛ العناصر غير المكتملة المؤرشفة قسرياً ليست ميزات مكتملة.', | ||
| empty: 'لا توجد تغييرات مؤرشفة بعد.', | ||
@@ -770,2 +863,7 @@ summary: 'الملخص', | ||
| knowledgeDocument: 'وثيقة change', | ||
| archiveStatus: 'حالة الأرشفة', | ||
| forceReason: 'سبب الأرشفة القسرية', | ||
| failingGates: 'البوابات غير المجتازة', | ||
| notRecorded: 'غير مسجل', | ||
| none: 'لا يوجد', | ||
| }; | ||
@@ -775,3 +873,3 @@ } | ||
| title: 'Project Feature Index', | ||
| guidance: 'Generated by OSpec. Use this file to locate completed behavior; open only the archived evidence or durable project documents needed for the current task.', | ||
| guidance: 'Generated by OSpec. Use this file to locate archived records; force-archived incomplete entries are marked and are not completed behavior.', | ||
| empty: 'No archived changes yet.', | ||
@@ -784,2 +882,7 @@ summary: 'Summary', | ||
| knowledgeDocument: 'Change knowledge document', | ||
| archiveStatus: 'Archive status', | ||
| forceReason: 'Force archive reason', | ||
| failingGates: 'Failing gates', | ||
| notRecorded: 'Not recorded', | ||
| none: 'None', | ||
| }; | ||
@@ -786,0 +889,0 @@ } |
@@ -87,2 +87,7 @@ import { ActiveChangeStatusItem, ActiveChangeStatusReport, DocsStatus, ExecutionStatus, KnowledgeDocInfo, ModuleInfo, ProjectMode, ProjectStructureStatus, ProjectSummary, SkillsStatus } from '../core/types'; | ||
| } | ||
| export interface FinalizeChangeOptions { | ||
| forceArchive?: boolean; | ||
| confirmForceArchive?: string; | ||
| reason?: string; | ||
| } | ||
| declare const REVIEW_ARTIFACT_ALLOWED_DECISIONS: readonly ["APPROVED", "APPROVED_WITH_CONCERNS", "NEEDS_CHANGES", "BLOCKED", "PENDING"]; | ||
@@ -188,3 +193,3 @@ export type ReviewArtifactDecision = typeof REVIEW_ARTIFACT_ALLOWED_DECISIONS[number]; | ||
| listActiveChangeNames(rootDir: string): Promise<string[]>; | ||
| finalizeChange(featurePath: string): Promise<{ | ||
| finalizeChange(featurePath: string, options?: FinalizeChangeOptions): Promise<{ | ||
| archivePath: string; | ||
@@ -237,4 +242,7 @@ change: ActiveChangeStatusItem; | ||
| rebuildIndex(rootDir: string): Promise<SkillsStatus['skillIndex']>; | ||
| private assertForceArchiveHasNoPendingLoopAction; | ||
| preflightArchivedKnowledgeWrite(projectRoot: string, archivePath: string): Promise<void>; | ||
| assertArchivedKnowledgeIndexed(projectRoot: string, archivePath: string): Promise<void>; | ||
| assertArchivedKnowledgeIndexed(projectRoot: string, archivePath: string, expectations?: { | ||
| disposition?: 'forced'; | ||
| }): Promise<void>; | ||
| private getDirectorySkeleton; | ||
@@ -241,0 +249,0 @@ private getProtocolShellDirectorySkeleton; |
+1
-1
| { | ||
| "name": "@clawplays/ospec-cli", | ||
| "version": "1.8.20", | ||
| "version": "1.8.21", | ||
| "description": "Official OSpec CLI package for spec-driven development (SDD) and document-driven development in AI coding agent and CLI workflows.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
+7
-0
@@ -176,2 +176,8 @@ <h1><a href="https://ospec.ai/" target="_blank" rel="noopener noreferrer">OSpec.ai</a></h1> | ||
| Explicit incomplete archive, only after the user accepts the unresolved risk: | ||
| ```bash | ||
| ospec finalize changes/active/<change-name> --force-archive --confirm-force-archive <exact-change-name> --reason "Accepted unresolved verification risk" | ||
| ``` | ||
| Archive notes: | ||
@@ -182,2 +188,3 @@ | ||
| - use `ospec finalize` to rebuild indexes and archive the accepted change | ||
| - force archive is never automatic: it requires the force flag, an exact change-name confirmation, and an audit reason; it refuses a pending Loop action and marks the archive `forced`, `incomplete`, and `accepted-risk` instead of completed | ||
| - new nested projects archive under `.ospec/changes/archived/YYYY-MM/YYYY-MM-DD/<change-name>`; CLI shorthand under `changes/archived/...` still works | ||
@@ -184,0 +191,0 @@ - existing flat archives are reorganized by `ospec update` |
+3
-1
@@ -58,3 +58,3 @@ --- | ||
| - Keep reviewers independent: they read scoped evidence, do not edit implementation, do not accept hidden severity downgrades, and report findings before summaries. Record actionable findings in both Markdown and the sibling structured `*.findings.json`. | ||
| - Do not archive while task graph, review, decision, documentation, optional-step, worker-status, or verification gates are unresolved. | ||
| - Do not archive while task graph, review, decision, documentation, optional-step, worker-status, or verification gates are unresolved during normal closeout. Force archive is an explicit user-directed exception only: first report the unresolved gates and `NOT_VERIFIED` evidence, then require `--force-archive`, an exact-name `--confirm-force-archive`, and a non-empty audit reason. Never infer this authorization from urgency, a blocker, or a request to "finish"; never run it while a Loop action is pending. | ||
@@ -69,2 +69,4 @@ Detailed goal artifacts live under `changes/active/<change>/artifacts/agents/` and `artifacts/reviews/`. Read their current packet or `for-ai/execution-protocol.md` for exact paths and commands instead of expanding the entire inventory here. | ||
| When the user explicitly accepts incomplete evidence and asks to force archive, use `ospec finalize [changes/active/<change>] --force-archive --confirm-force-archive <exact-change-name> --reason "<accepted risk>"`. This bypasses completion gates only. It preserves failed checks and pending state, writes `artifacts/agents/force-archive.json`, and marks generated knowledge as `forced`, `incomplete`, and `accepted-risk`; it must never be described as completed behavior. | ||
| After archive, verify: | ||
@@ -71,0 +73,0 @@ |
Sorry, the diff of this file is too big to display
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
3418098
0.72%59781
0.57%399
1.79%69
1.47%