@peterxiaoyang/superspec 0.1.13 → 0.1.14

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.
@@ -1,5 +1,5 @@
1
1
  import { block, commandExists, GuardError, printDecision, reason, runCommand, openspec_cli_probe, parseDecisionOutputFormat, REQUIRED_OPENSPEC_MIN_VERSION, } from "./core.js";
2
- import { forced_openspec_install_plan, project_init, recommended_openspec_install_plan } from "./project_init.js";
2
+ import { ensure_openspec_chinese_context, forced_openspec_install_plan, project_init, recommended_openspec_install_plan } from "./project_init.js";
3
3
  import { install_workflow, uninstall_workflow, update_workflow } from "./install_engine.js";
4
4
  import { homedir } from "node:os";
5
5
  import { resolve } from "node:path";
@@ -320,6 +320,11 @@ function run_init(args, scope) {
320
320
  summary = engineDecision(`${gatePrefix}_update`, targetRoot, update_workflow(targetRoot, { scope }), [
321
321
  scope === "project" ? "review *.new files for user-modified surfaces, then rerun superspec check check-init" : "review *.new files for user-modified user-level surfaces",
322
322
  ]);
323
+ if (scope === "project" && summary.allowed) {
324
+ const act = ensure_openspec_chinese_context(targetRoot);
325
+ if (Array.isArray(summary.actions))
326
+ summary.actions.push(act);
327
+ }
323
328
  }
324
329
  else if (scope === "user") {
325
330
  summary = engineDecision("user_install", targetRoot, install_workflow(targetRoot, { force: args.force, scope: "user" }), [
@@ -10,7 +10,7 @@ import { final_verification_evidences, index_evidence, live_pass, live_user_conf
10
10
  import { file_blob_sha, dirty_worktree_paths } from "./git.js";
11
11
  import { preset_upgrade_reasons, preset_upgrade_required_from_context } from "./archive.js";
12
12
  import { state_corrupt_reasons, state_stale_reasons } from "./state.js";
13
- import { parse_tasks, resolve_test_contract_command, splitList, task_apply_execution_surface, task_apply_execution_surface_reasons, test_contract_invariant_refs_by_test, } from "./tasks.js";
13
+ import { parse_tasks, resolve_test_contract_command, splitList, task_apply_execution_surface, task_apply_execution_surface_reasons, tasks_structure_hash, test_contract_invariant_refs_by_test, } from "./tasks.js";
14
14
  import { APPLY_CODE_REVIEW_REPORT_REQUIRED_FIELDS, APPLY_EXECUTOR_REPORT_REQUIRED_FIELDS, APPLY_TEST_RUNNER_REPORT_REQUIRED_FIELDS, APPLY_VERIFIER_REPORT_REQUIRED_FIELDS, apply_worker_implementation_fingerprint, apply_worker_executor_input_ref_digest, apply_worker_protected_path_refs, compute_apply_worker_freshness, fingerprint_digest, fingerprint_matches, pinned_artifact_ref_reasons as shared_pinned_artifact_ref_reasons, pre_edit_evidence_ref_reasons, pre_edit_worker_test_run_reasons, read_pinned_artifact_json, worker_input_ref_digest, worker_test_run_reasons, } from "./apply_worker_chain.js";
15
15
  import { apply_worker_chain_lifecycle_state } from "./apply_worker_chain_lifecycle.js";
16
16
  import { PACKAGE_ROOT } from "./install_engine.js";
@@ -400,6 +400,14 @@ function workflow_packet(ctx, gateRaw, taskId) {
400
400
  diagnostic_command: gate_recheck_command(ctx.change, gate, taskId, true),
401
401
  must_read_refs: workflow_gate_refs(ctx, gate),
402
402
  };
403
+ // Surface tasks_structure_hash directly in the agent-facing packet so apply_isolation and
404
+ // scope_expansion human confirmations can be constructed without a second status/json detour
405
+ // or source-code inspection to find the current tasks.md structural fingerprint.
406
+ if (gate === "apply_ready" || gate === "task_edit" || gate === "task_complete" || gate === "task_reopen") {
407
+ const currentTasksStructureHash = tasks_structure_hash(ctx.changeRoot);
408
+ if (currentTasksStructureHash)
409
+ packet.tasks_structure_hash = currentTasksStructureHash;
410
+ }
403
411
  const cliSurfaces = openspec_cli_surfaces_for_gate(ctx.change, gate);
404
412
  if (cliSurfaces.length > 0)
405
413
  packet.openspec_cli_surfaces = cliSurfaces;
@@ -1,6 +1,6 @@
1
1
  export type PacketOutputFormat = "agent" | "prompt";
2
2
  export type PinnedRef = {
3
- root: "repo" | "change";
3
+ root: "repo" | "change" | "superspec";
4
4
  path: string;
5
5
  blob_sha: string;
6
6
  };
@@ -19,6 +19,7 @@ export type WorkflowPacket = {
19
19
  current_gate: string;
20
20
  task_id?: string;
21
21
  status: "allowed" | "blocked";
22
+ tasks_structure_hash?: string;
22
23
  top_blockers?: string[];
23
24
  blocker_count?: number;
24
25
  has_more_blockers?: boolean;
@@ -1,4 +1,5 @@
1
1
  import { type JsonMap, type OpenspecCliProbe } from "./core.ts";
2
+ import { type EngineAction } from "./install_engine.ts";
2
3
  export declare const OPENSPEC_NPM_PACKAGE = "@fission-ai/openspec";
3
4
  export declare const OPENSPEC_INSTALL_DOC_URL = "https://github.com/Fission-AI/OpenSpec#readme";
4
5
  export type OpenspecInstallPlan = {
@@ -26,6 +27,7 @@ export declare function openspec_cli_requirement_message(probe: OpenspecCliProbe
26
27
  cwd?: string;
27
28
  }) => boolean;
28
29
  }): string;
30
+ export declare function ensure_openspec_chinese_context(repoRoot: string): EngineAction;
29
31
  export declare function project_init(repoRootRaw?: string, opts?: {
30
32
  force?: boolean;
31
33
  }): JsonMap;
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
2
+ import { dirname, join, resolve } from "node:path";
3
3
  import { REQUIRED_SUPERSPEC_AGENT_ROLES, REQUIRED_OPENSPEC_MIN_VERSION, block, commandExists, openspec_cli_probe, reason, read_agent_toml_name, } from "./core.js";
4
4
  import { install_workflow } from "./install_engine.js";
5
5
  export const OPENSPEC_NPM_PACKAGE = "@fission-ai/openspec";
@@ -136,9 +136,33 @@ function ensureSuperSpecWorkflow(repoRoot, actions, force) {
136
136
  }
137
137
  return result.problems;
138
138
  }
139
+ const OPENSPEC_CONFIG_REL = "openspec/config.yaml";
140
+ const ZH_CTX = '所有文档必须使用中文编写。需求描述应使用"应当"或"必须"等词汇。';
141
+ export function ensure_openspec_chinese_context(repoRoot) {
142
+ const p = join(repoRoot, OPENSPEC_CONFIG_REL);
143
+ if (existsSync(p) && statSync(p).isFile()) {
144
+ const t = readFileSync(p, "utf8");
145
+ if (/context:[\s\S]*?所有文档必须使用中文/u.test(t))
146
+ return { action: `configure ${OPENSPEC_CONFIG_REL}`, status: "ok", detail: "OpenSpec 中文语境已存在" };
147
+ if (/^context:/mu.test(t)) {
148
+ const u = t.replace(/^context:\s*[|>]?\s*\n(?:[ \t].*\n)*/mu, `context: |\n ${ZH_CTX}\n`);
149
+ mkdirSync(dirname(p), { recursive: true });
150
+ writeFileSync(p, u, "utf8");
151
+ return { action: `configure ${OPENSPEC_CONFIG_REL}`, status: "updated", detail: "OpenSpec context 已替换为简体中文语境" };
152
+ }
153
+ const s = t.endsWith("\n") ? "\ncontext: |\n" : "\n\ncontext: |\n";
154
+ mkdirSync(dirname(p), { recursive: true });
155
+ writeFileSync(p, `${t}${s} ${ZH_CTX}\n`, "utf8");
156
+ return { action: `configure ${OPENSPEC_CONFIG_REL}`, status: "updated", detail: "OpenSpec context 已追加简体中文语境" };
157
+ }
158
+ mkdirSync(dirname(p), { recursive: true });
159
+ writeFileSync(p, `context: |\n ${ZH_CTX}\n`, "utf8");
160
+ return { action: `configure ${OPENSPEC_CONFIG_REL}`, status: "created", detail: "OpenSpec 输出语言已设为简体中文" };
161
+ }
139
162
  export function project_init(repoRootRaw = process.cwd(), opts = {}) {
140
163
  const repoRoot = resolve(repoRootRaw);
141
164
  const actions = [];
165
+ actions.push(ensure_openspec_chinese_context(repoRoot));
142
166
  const problems = [
143
167
  ...ensureOpenSpecCliSurface(repoRoot, actions),
144
168
  ...ensureSuperSpecWorkflow(repoRoot, actions, opts.force === true),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterxiaoyang/superspec",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "SuperSpec workflow package: guard runtime, generic workflow templates, and Codex adapter payload.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,7 +22,7 @@ metadata:
22
22
 
23
23
  ## 阶段职责
24
24
 
25
- Propose 把 discovery 转成 OpenSpec proposal package,并补 SuperSpec 业务约束和测试契约。负责的文件是 `proposal.md`、`specs/**/*.md`、`design.md`、`tasks.md`、`.superspec/artifacts/business-invariants.md`、`.superspec/artifacts/test-contract.md`。本阶段不改实现代码。
25
+ Propose 把 discovery 转成 OpenSpec proposal package,并按阶段生成 SuperSpec 业务约束和测试契约辅助文档。负责产出 `proposal.md`、`specs/**/*.md`、`design.md`、`tasks.md`,以及在需要时新建或更新 `.superspec/artifacts/business-invariants.md`、`.superspec/artifacts/test-contract.md`。本阶段不改实现代码。
26
26
 
27
27
  ## 第一条必跑命令
28
28
 
@@ -49,7 +49,10 @@ openspec instructions <artifact-id> --change "<change>" --json
49
49
  1. 运行 `openspec status --change "<change>" --json`,取 artifact 顺序、状态和 `resolvedOutputPath`。
50
50
  2. 每个 artifact(proposal/specs/design/tasks)运行 `openspec instructions <artifact-id> --change "<change>" --json`,取回 template/rules/context/instruction/dependencies。
51
51
  3. 先读 dependencies 列出的已完成 artifact,再按 template 写到 `resolvedOutputPath`;context/rules 是对作者的约束,不写进产出文件。
52
- 4. sidecar(`.superspec/artifacts/business-invariants.md`、`.superspec/artifacts/test-contract.md`)用 SuperSpec 内置模板,写到 change 内 `.superspec/artifacts/`。
52
+ 4. 辅助文档(`.superspec/artifacts/business-invariants.md`、`.superspec/artifacts/test-contract.md`)如果尚不存在,就按 SuperSpec 内置模板在 change 内 `.superspec/artifacts/` 新建;已存在则基于当前阶段要求更新。
53
+ 5. `invariants_reviewed` 的 `workflow-packet` 会下发 `business_invariants_template` 与 `business_invariants_rules`;必须按该骨架直接填写,不得改成段落/列表。
54
+ 6. `test_contract_drafted` 的 `workflow-packet` 会下发 `test_contract_template` 与 `test_contract_rules`;必须按该骨架直接填写,不得改成段落/列表。
55
+ 7. `business-invariants.md` 的 `## Invariants` / `## Mapping` 和 `test-contract.md` 的 `## 测试覆盖矩阵` 必须保持 Markdown 表格,列名原样保留。
53
56
 
54
57
  ## Packet 驱动的阶段门
55
58