🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@clawplays/ospec-cli

Package Overview
Dependencies
Maintainers
1
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@clawplays/ospec-cli - npm Package Compare versions

Comparing version
1.8.11
to
1.8.12
+1
-0
assets/for-ai/ar/execution-protocol.md

@@ -56,2 +56,3 @@ ---

- استخدم `ospec execute dispatch [changes/active/<change>] [--task task-id] [--limit N]` لإنشاء batch آمن للتوازي من worker packets و`artifacts/agents/execution-session.json`؛ يتضمن كل packet project session brief snapshot وworker profile يوضح capability tier وrecommended target وtarget tool mapping وrationale وrequired behavior. استخدم `ospec execute complete <task-id> ...` لتسجيل نتائج worker. استخدم `--task` لمهمة واحدة صريحة و`--limit` لتحديد حجم dispatch batch. تزامن هذه الأوامر أيضا `artifacts/agents/worker-status.md`، وتحدّث OSpec artifacts فقط ولا تشغّل workers خارجيين؛ عندما تكون النتيجة `NEEDS_CONTEXT` أو `BLOCKED` يكتب `complete` ملفات blocker escalation تحت `artifacts/agents/blockers/`
- لا تضع القبول الخارجي أو اليدوي في المسار الحرج لمهام تنفيذ غير مرتبطة. إذا كان التنفيذ الدائم مسجلاً وكانت المهمة `BLOCKED` فقط لغياب القبول الخارجي، وبعد تفويض المستخدم الصريح لتأجيله إلى البوابة النهائية، شغّل `ospec execute defer-blocker <task-id> [change-path] --reason "..."`. تبقى المهمة محظورة وغير محددة، ويستمر فقط التنفيذ الآمن من ناحية التبعيات؛ وتظل final review وverify وfinalize وarchive محظورة حتى توفر الدليل الحقيقي
- توفير الـ tokens (لا يغيّر أي خطوة): مرّر `--brief` على `ospec execute …` لقراءة ملخّص مختصر بدل التقرير الكامل، وقُد كل خطوة من `ospec execute status --brief` بدل إعادة قراءة `task-graph.json` أو `worker-status.md` أو `launch-plan.md` الكاملة في كل دورة — تُكتب الـ artifacts كاملةً على القرص، فافتحها فقط عند الحاجة للتفاصيل

@@ -58,0 +59,0 @@ - بعد dispatch، استخدم `ospec execute launch [changes/active/<change>] [--task task-id] [--target codex|gpt|claude|gemini|opencode|cursor|copilot] [--dry-run] [--json]` لكتابة launch plan؛ يقبل `runtimeAdapter` فقط model-native subagent capability حالية ومرتبطة بالـ target. ينشئ single-task dispatch هذه الخطة تلقائيا

@@ -58,2 +58,3 @@ ---

- Use `ospec execute dispatch [changes/active/<change>] [--task task-id] [--limit N]` to create a parallel-safe batch of worker packets and `artifacts/agents/execution-session.json`; each packet includes the project session brief snapshot and a worker profile with capability tier, recommended target, target tool mapping, rationale, and required behavior. Use `ospec execute complete <task-id> ...` to record worker results. Use `--task` for one explicit task and `--limit` to cap dispatch batch size. These commands also sync `artifacts/agents/worker-status.md`, update OSpec artifacts only, and do not launch external workers; required pending user decisions block dispatch; `complete` writes blocker escalation artifacts under `artifacts/agents/blockers/` when the result is `NEEDS_CONTEXT` or `BLOCKED`
- Keep external/manual acceptance out of the critical path for unrelated implementation tasks. If a durable implementation is already recorded as `BLOCKED` only because external acceptance is unavailable, and the user explicitly authorizes deferring that acceptance, run `ospec execute defer-blocker <task-id> [change-path] --reason "..."`. This leaves the task blocked and unchecked while dependency-safe implementation continues; final review, verification, finalization, and archive remain blocked until the real evidence exists
- Use `ospec execute launch [changes/active/<change>] [--task task-id] [--target codex|gpt|claude|gemini|opencode|cursor|copilot] [--dry-run] [--json]` after dispatch to write the launch plan. Its `runtimeAdapter` accepts only a current, target-bound model-native subagent capability. A single-task dispatch generates this plan automatically

@@ -60,0 +61,0 @@ - Execute `runtimeAdapter.selected.nativeSubagent`; parallelize only the safe batch. Missing or expired native capability blocks dispatch with no agent CLI or controller-context fallback

@@ -56,2 +56,3 @@ ---

- `ospec execute dispatch [changes/active/<change>] [--task task-id] [--limit N]` で parallel-safe な worker packet batch と `artifacts/agents/execution-session.json` を作成する。各 packet には project session brief snapshot と、capability tier、recommended target、target tool mapping、rationale、required behavior を示す worker profile が含まれる。`ospec execute complete <task-id> ...` で worker 結果を記録する。`--task` は明示的な単一 task、`--limit` は dispatch batch size の上限に使う。これらのコマンドは `artifacts/agents/worker-status.md` も同期し、OSpec artifacts のみを更新し、外部 worker は起動しない。結果が `NEEDS_CONTEXT` または `BLOCKED` の場合、`complete` は `artifacts/agents/blockers/` に blocker escalation を書く
- 外部/手動 acceptance を無関係な実装 task の critical path に置かない。durable implementation があり、外部 acceptance だけが未取得で `BLOCKED` の場合、ユーザーが final gate への延期を明示的に承認した後で `ospec execute defer-blocker <task-id> [change-path] --reason "..."` を実行する。task は blocked・未チェックのまま dependency-safe な実装だけを続行し、実証が揃うまで final review、verify、finalize、archive は引き続きブロックする
- dispatch 後は `ospec execute launch [changes/active/<change>] [--task task-id] [--target codex|gpt|claude|gemini|opencode|cursor|copilot] [--dry-run] [--json]` で launch plan を書く。`runtimeAdapter` は current かつ target-bound な model-native subagent capability のみを受け入れる。single-task dispatch はこの plan を自動生成する

@@ -58,0 +59,0 @@ - `runtimeAdapter.selected.nativeSubagent` を実行し、安全な batch だけを並列 dispatch する。capability がない、または期限切れの場合は block し、agent CLI や current controller に fallback しない

@@ -58,2 +58,3 @@ ---

- 用 `ospec execute dispatch [changes/active/<change>] [--task task-id] [--limit N]` 生成并行安全的 worker 任务包批次和 `artifacts/agents/execution-session.json`;每个 packet 都包含 project session brief snapshot 和 worker profile,说明 capability tier、recommended target、target tool mapping、rationale 和 required behavior。用 `ospec execute complete <task-id> ...` 记录 worker 结果。用 `--task` 指定单个任务,用 `--limit` 限制派发批次大小。这些命令也会同步 `artifacts/agents/worker-status.md`,只更新 OSpec artifacts,不启动外部 worker;required pending user decision 会阻止 dispatch;当结果是 `NEEDS_CONTEXT` 或 `BLOCKED` 时,`complete` 会在 `artifacts/agents/blockers/` 下写入 blocker escalation
- 不要把外部/人工验收放在无关开发任务的关键路径上。如果已有 durable implementation 仅因外部验收不可用而记录为 `BLOCKED`,且用户明确授权把该验收延期到最终门,运行 `ospec execute defer-blocker <task-id> [change-path] --reason "..."`。该 task 仍保持阻塞和未勾选,只允许依赖安全的实现继续;真实证据到达前,final review、verify、finalize 和 archive 仍然阻断
- 省 token(不改变任何步骤):`ospec execute …` 命令带 `--brief` 读精简摘要而非完整报告;用 `ospec execute status --brief` 驱动每一步,不要每轮都重读完整的 `task-graph.json`、`worker-status.md` 或 `launch-plan.md`——产物仍完整写盘,只在需要细节时才打开

@@ -60,0 +61,0 @@ - dispatch 后用 `ospec execute launch [changes/active/<change>] [--task task-id] [--target codex|gpt|claude|gemini|opencode|cursor|copilot] [--dry-run] [--json]` 写入启动计划;`runtimeAdapter` 只接受当前、target 匹配的模型原生 subagent capability;单任务 dispatch 会自动生成该计划

+3
-1

@@ -27,2 +27,3 @@ ---

- **Review repair is convergent and regression-aware.** A downstream task that shares target files inherits transitive upstream regression obligations. The default two-round values are convergence thresholds. Changed structured finding IDs continue automatically. A stable ID may also continue only when both its structured finding fingerprint and its authorized repair-scope code snapshot changed; wording-only or code-only churn, exact repeats, and cycles stop. A blocked final review requires blocker resolution and must not enter grouped repair. Never raise a limit to bypass unchanged evidence.
- **External acceptance may be deferred, never waived.** Keep device, credential, manual, and third-party acceptance out of unrelated implementation critical paths. When durable implementation exists and the user explicitly authorizes moving only external acceptance to the final gate, use `ospec execute defer-blocker`; the task stays `BLOCKED` and unchecked, while dependency-safe implementation may continue. Final review, verification, finalization, and archive remain hard-blocked until the real evidence exists.
- **Allowlist updates are replacement or explicit CAS.** Repeated `loop configure --allow-*` calls replace the complete selected list and print a diff; they never append. For L3 derive/check/apply the exact task-graph permissions, review the diff, and pass `--approve-expansion` only for an intended expansion.

@@ -82,3 +83,3 @@ - **Stop condition is three-stage:** run the project's real tests, record evidence with `ospec execute verify --status`, then confirm with `ospec verify`.

6. Draft or update `implementation-plan.md` from `design.md`; identify target files, expected results, verification commands, dependencies, parallelizable work, and conflicts.
7. Derive `artifacts/agents/task-graph.json` from `implementation-plan.md`; give every task a `documentation_updates` array (`[]` when none), include every declared docs path in the same task's `target_files`, and require meaningful-change evidence from dispatch to completion; derive `tasks.md` from the task graph. Mark every dependency/file-safe task `parallelizable: true`; every generated `parallelizable: false` task must include `serial_reason`, and `maxParallel=1` must include `maxParallelReason`. Split tasks with more than six `target_files`; when one atomic verification boundary genuinely requires that breadth, record a concrete `scope_reason`. For L3 use `ospec loop allowlist derive/check/apply --from-task-graph` so exact paths and commands fail before expensive document reviewers run; never assume repeated configure calls append. Finalize also generates one indexed `docs/project/changes/<archive-path>.md` for this goal; its preflight refuses to overwrite a human-owned file at that path, and the generated summary does not replace required architecture, API, module, or operational documentation.
7. Derive `artifacts/agents/task-graph.json` from `implementation-plan.md`; give every task a `documentation_updates` array (`[]` when none), include every declared docs path in the same task's `target_files`, and require meaningful-change evidence from dispatch to completion; derive `tasks.md` from the task graph. Mark every dependency/file-safe task `parallelizable: true`; every generated `parallelizable: false` task must include `serial_reason`, and `maxParallel=1` must include `maxParallelReason`. Split tasks with more than six `target_files`; when one atomic verification boundary genuinely requires that breadth, record a concrete `scope_reason`. Split implementation/automatic checks from external device, credential, third-party, or manual acceptance so an unavailable external gate cannot block unrelated implementation; downstream work may depend on the implementation slice, while the external acceptance remains a final hard gate. For L3 use `ospec loop allowlist derive/check/apply --from-task-graph` so exact paths and commands fail before expensive document reviewers run; never assume repeated configure calls append. Finalize also generates one indexed `docs/project/changes/<archive-path>.md` for this goal; its preflight refuses to overwrite a human-owned file at that path, and the generated summary does not replace required architecture, API, module, or operational documentation.
8. Run `ospec execute doc-review [changes/active/<goal>] --stage design`. When it reports `Reused approval: yes`, do not launch another reviewer. Otherwise execute one fresh independent design reviewer through `dispatch.runtimeAdapter`, immediately claim its real executor id, wait for Markdown plus structured findings, then complete that executor. Approval must validate before plan review.

@@ -136,2 +137,3 @@ 9. Run `ospec execute doc-review [changes/active/<goal>] --stage plan`. Reuse a valid approval; otherwise execute one fresh independent plan reviewer through `dispatch.runtimeAdapter`, claim its real executor id, wait for findings, then complete it before worker dispatch. The controlling AI must not self-approve either review.

ospec execute complete <task-id> [changes/active/<goal>] --status DONE --usage-file usage.json
ospec execute defer-blocker <task-id> [changes/active/<goal>] --reason "User authorized external acceptance at final review"
ospec loop tick [changes/active/<goal>] # controller-owned task/final review with real executor provenance

@@ -138,0 +140,0 @@ ospec execute review [changes/active/<goal>] --task task-id # non-controller workflow only

@@ -65,3 +65,3 @@ #!/usr/bin/env node

const services_1 = require("./services");
const CLI_VERSION = '1.8.11';
const CLI_VERSION = '1.8.12';
function showInitUsage() {

@@ -567,2 +567,3 @@ console.log('Usage: ospec init [root-dir] [--summary "..."] [--tech-stack node,react] [--architecture "..."] [--document-language en-US|zh-CN|ja-JP|ar]');

ospec execute complete task-1 ./changes/active/onboarding-flow --status DONE --usage-file ./usage.json
ospec execute defer-blocker task-1 ./changes/active/onboarding-flow --reason "Device acceptance will be completed before final review"
ospec execute repair ./changes/active/onboarding-flow

@@ -569,0 +570,0 @@ ospec loop heartbeat ./changes/active/onboarding-flow --action-item worker-1 --executor child-1

@@ -31,2 +31,3 @@ import { TaskDocumentReviewStage, TaskHandoffTarget, TaskWorkerToolTarget, TaskUserDecisionOption } from '../services/TaskGraphExecutionService';

private debug;
private deferBlocker;
private resolveChangePath;

@@ -101,2 +102,3 @@ private printStatus;

private parseCompleteArgs;
private parseDeferBlockerArgs;
private parseCollectArgs;

@@ -103,0 +105,0 @@ private parseRetryArgs;

import { SkillIndex } from '../core/types';
import { SkillParser } from './SkillParser';
export interface IndexWriteResult {
index: SkillIndex;
managedPaths: string[];
removedPaths: string[];
}
export declare class IndexBuilder {

@@ -9,2 +14,3 @@ private skillParser;

write(rootDir: string): Promise<SkillIndex>;
writeWithSummary(rootDir: string): Promise<IndexWriteResult>;
createEmpty(rootDir: string): Promise<SkillIndex>;

@@ -17,2 +23,6 @@ private stripVolatileFields;

private scanArchivedChanges;
private scanArchivedChangesWithHistory;
private mergeHistoricalStringLists;
private mergeHistoricalOrderedLists;
private readArchivedChangeHistory;
private readArchivedChange;

@@ -19,0 +29,0 @@ private writeArchivedChangeKnowledgeDocuments;

@@ -8,2 +8,3 @@ "use strict";

const fs_1 = require("fs");
const child_process_1 = require("child_process");
const path_1 = __importDefault(require("path"));

@@ -41,3 +42,3 @@ const constants_1 = require("../core/constants");

const config = await this.readProjectConfig(rootDir);
const archivedChanges = await this.scanArchivedChanges(rootDir, config);
const archivedChanges = await this.scanArchivedChangesWithHistory(rootDir, config);
return this.buildSnapshot(rootDir, config, archivedChanges);

@@ -128,6 +129,9 @@ }

async write(rootDir) {
return (await this.writeWithSummary(rootDir)).index;
}
async writeWithSummary(rootDir) {
const config = await this.readProjectConfig(rootDir);
const archivedChanges = await this.scanArchivedChanges(rootDir, config);
await this.writeArchivedChangeKnowledgeDocuments(rootDir, config, archivedChanges);
await this.writeFeatureIndex(rootDir, config, archivedChanges);
const archivedChanges = await this.scanArchivedChangesWithHistory(rootDir, config);
const knowledgeWrite = await this.writeArchivedChangeKnowledgeDocuments(rootDir, config, archivedChanges);
const featureIndexPath = await this.writeFeatureIndex(rootDir, config, archivedChanges);
const indexPath = (0, ProjectLayout_1.resolveManagedPath)(rootDir, constants_1.FILE_NAMES.SKILL_INDEX, config);

@@ -140,11 +144,24 @@ const previous = (await pathExists(indexPath))

const nextComparable = this.stripVolatileFields(index);
let writtenIndex = index;
if (previous && JSON.stringify(previousComparable) === JSON.stringify(nextComparable)) {
return previous;
writtenIndex = previous;
}
const output = {
...index,
generated: new Date().toISOString(),
else {
writtenIndex = {
...index,
generated: new Date().toISOString(),
};
await fs_1.promises.writeFile(indexPath, JSON.stringify(writtenIndex, null, 2), 'utf-8');
}
const managedPaths = Array.from(new Set([
...knowledgeWrite.managedPaths,
...knowledgeWrite.removedPaths,
...(featureIndexPath ? [featureIndexPath] : []),
path_1.default.relative(rootDir, indexPath).replace(/\\/g, '/'),
])).sort((left, right) => left.localeCompare(right));
return {
index: writtenIndex,
managedPaths,
removedPaths: knowledgeWrite.removedPaths,
};
await fs_1.promises.writeFile(indexPath, JSON.stringify(output, null, 2), 'utf-8');
return output;
}

@@ -291,2 +308,63 @@ async createEmpty(rootDir) {

}
async scanArchivedChangesWithHistory(rootDir, config) {
const current = await this.scanArchivedChanges(rootDir, config);
const historical = await this.readArchivedChangeHistory(rootDir, config);
const historicalByArchive = new Map();
for (const entry of historical) {
const archive = String(entry?.archive || '').replace(/\\/g, '/');
if (!archive)
continue;
const entries = historicalByArchive.get(archive) || [];
entries.push(entry);
historicalByArchive.set(archive, entries);
}
return current.map(entry => {
const history = historicalByArchive.get(entry.archive.replace(/\\/g, '/')) || [];
return {
...entry,
target_files: this.mergeHistoricalStringLists(history, entry.target_files, 'target_files'),
verification_commands: this.mergeHistoricalStringLists(history, entry.verification_commands, 'verification_commands'),
project_documents: this.mergeHistoricalStringLists(history, entry.project_documents, 'project_documents'),
documents: this.mergeHistoricalOrderedLists(history, entry.documents, 'documents'),
};
});
}
mergeHistoricalStringLists(historical, current, key) {
return Array.from(new Set([
...historical.flatMap(entry => Array.isArray(entry?.[key]) ? entry[key] || [] : []),
...(current || []),
].map(value => String(value || '').trim()).filter(Boolean))).sort();
}
mergeHistoricalOrderedLists(historical, current, key) {
return Array.from(new Set([
...historical.flatMap(entry => Array.isArray(entry?.[key]) ? entry[key] || [] : []),
...(current || []),
].map(value => String(value || '').trim()).filter(Boolean)));
}
async readArchivedChangeHistory(rootDir, config) {
const indexPath = (0, ProjectLayout_1.resolveManagedPath)(rootDir, constants_1.FILE_NAMES.SKILL_INDEX, config);
const candidates = [];
if (await pathExists(indexPath)) {
candidates.push(await fs_1.promises.readFile(indexPath, 'utf8'));
}
const relativeIndexPath = path_1.default.relative(rootDir, indexPath).replace(/\\/g, '/');
try {
candidates.push((0, child_process_1.execFileSync)('git', ['-C', rootDir, 'show', `HEAD:${relativeIndexPath}`], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
}
catch {
// Non-git projects still retain history from their current generated index.
}
const entries = [];
for (const candidate of candidates) {
try {
const parsed = JSON.parse(candidate.replace(/^\uFEFF/, ''));
if (Array.isArray(parsed?.archived_changes))
entries.push(...parsed.archived_changes);
}
catch {
// A damaged historical index must not prevent a fresh index build.
}
}
return entries;
}
async readArchivedChange(rootDir, archiveDir) {

@@ -386,2 +464,3 @@ try {

const expectedPaths = new Set();
const managedPaths = [];
const copy = this.getArchivedKnowledgeCopy(config?.documentLanguage);

@@ -403,2 +482,3 @@ for (const change of archivedChanges) {

const knowledgeDocument = path_1.default.relative(rootDir, resolvedTarget).replace(/\\/g, '/');
managedPaths.push(knowledgeDocument);
change.knowledge_document = knowledgeDocument;

@@ -465,3 +545,7 @@ await this.assertGeneratedKnowledgeDocumentReplaceable(resolvedTarget, change.archive);

}
await this.removeStaleArchivedKnowledgeDocuments(knowledgeRoot, expectedPaths);
const removedPaths = await this.removeStaleArchivedKnowledgeDocuments(rootDir, knowledgeRoot, expectedPaths);
return {
managedPaths: managedPaths.sort((left, right) => left.localeCompare(right)),
removedPaths,
};
}

@@ -505,5 +589,6 @@ getKnowledgeDocumentRelativePath(archive) {

}
async removeStaleArchivedKnowledgeDocuments(knowledgeRoot, expectedPaths) {
async removeStaleArchivedKnowledgeDocuments(rootDir, knowledgeRoot, expectedPaths) {
if (!(await pathExists(knowledgeRoot)))
return;
return [];
const removedPaths = [];
const visit = async (currentDir) => {

@@ -523,2 +608,3 @@ const entries = await fs_1.promises.readdir(currentDir, { withFileTypes: true });

await fs_1.promises.unlink(fullPath);
removedPaths.push(path_1.default.relative(rootDir, fullPath).replace(/\\/g, '/'));
}

@@ -532,2 +618,3 @@ }

await visit(knowledgeRoot);
return removedPaths.sort((left, right) => left.localeCompare(right));
}

@@ -590,3 +677,3 @@ getArchivedKnowledgeCopy(documentLanguage) {

if (!(await pathExists(docsProjectRoot)) && archivedChanges.length === 0)
return;
return null;
await fs_1.promises.mkdir(docsProjectRoot, { recursive: true });

@@ -637,2 +724,3 @@ const targetPath = path_1.default.join(docsProjectRoot, 'feature-index.md');

await fs_1.promises.writeFile(targetPath, content, 'utf8');
return path_1.default.relative(rootDir, targetPath).replace(/\\/g, '/');
}

@@ -639,0 +727,0 @@ getFeatureIndexCopy(documentLanguage) {

@@ -147,2 +147,3 @@ "use strict";

ospec execute complete <task-id> [change-path|project-path] [--status DONE|DONE_WITH_CONCERNS|NEEDS_CONTEXT|BLOCKED] [--summary "..."] [--usage-file usage.json] - record a worker result and ingest the dispatch usage sidecar automatically when present; NEEDS_CONTEXT/BLOCKED writes blocker escalation artifacts
ospec execute defer-blocker <task-id> [change-path|project-path] --reason "..." - explicitly defer a durable external acceptance blocker to final review so dependency-safe implementation can continue; the task stays BLOCKED and finalization/archive remain gated
ospec execute sync [change-path|project-path] - sync worker status, bootstrap/state.json, and the project session brief

@@ -149,0 +150,0 @@ ospec execute review [change-path|project-path] [--task task-id] [--stage spec|quality] - create a non-controller task-level or final review packet; controller Goals use ospec loop tick

{
"name": "@clawplays/ospec-cli",
"version": "1.8.11",
"version": "1.8.12",
"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",

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display