@peterxiaoyang/superspec 0.1.10 → 0.1.12
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 +8 -10
- package/dist/src/apply_worker_chain.d.ts +6 -3
- package/dist/src/apply_worker_chain.js +125 -30
- package/dist/src/apply_worker_chain_lifecycle.d.ts +7 -5
- package/dist/src/apply_worker_chain_lifecycle.js +153 -22
- package/dist/src/cli_args.d.ts +1 -0
- package/dist/src/cli_args.js +33 -15
- package/dist/src/disclosure.js +2 -2
- package/dist/src/evidence.js +104 -8
- package/dist/src/gates.d.ts +1 -0
- package/dist/src/gates.js +160 -27
- package/dist/src/hooks/adapter.js +43 -7
- package/dist/src/hooks/guard_api.js +2 -2
- package/dist/src/hooks/health.js +0 -12
- package/dist/src/hooks/policy_event.js +11 -1
- package/dist/src/i18n.js +63 -3
- package/dist/src/init_cli.js +1 -1
- package/dist/src/install_engine.js +64 -0
- package/dist/src/openspec.d.ts +2 -0
- package/dist/src/openspec.js +31 -0
- package/dist/src/packet_render.d.ts +1 -0
- package/dist/src/packet_render.js +248 -31
- package/dist/src/packet_schema.d.ts +2 -0
- package/dist/src/tasks.d.ts +5 -0
- package/dist/src/tasks.js +62 -0
- package/dist/src/util.d.ts +4 -0
- package/dist/src/util.js +13 -0
- package/dist/superspec.js +4 -4
- package/package.json +3 -3
- package/templates/hooks/codex-hooks.json +0 -26
- package/templates/sidecar/discovery.md +9 -0
- package/templates/sidecar/test-contract.md +2 -1
- package/templates/workflow/prompts/code-reviewer.md +1 -1
- package/templates/workflow/prompts/executor.md +1 -1
- package/templates/workflow/prompts/test-engineer.md +1 -0
- package/templates/workflow/prompts/test-runner.md +3 -1
- package/templates/workflow/prompts/verifier.md +9 -9
- package/templates/workflow/skills/superspec-apply/SKILL.md +58 -40
- package/templates/workflow/skills/superspec-archive/SKILL.md +5 -13
- package/templates/workflow/skills/superspec-explore/SKILL.md +17 -18
- package/templates/workflow/skills/superspec-propose/SKILL.md +26 -22
- package/templates/workflow/skills/superspec-review/SKILL.md +17 -24
- package/dist/src/packet_measure.d.ts +0 -43
- package/dist/src/packet_measure.js +0 -382
- /package/bin/{superspec-guard.js → superspec-check.js} +0 -0
package/dist/src/gates.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join, relative } from "node:path";
|
|
3
|
-
import { ARTIFACT_ENTER_GATE, MAIN_ADJUDICATION_DECISIONS, REQUEST_CHANGES_ROUTES, NO_TDD_REASONS, OPENSPEC_ARTIFACTS, FINAL_VERIFICATION_ROLES, REQUIRED_SUPERSPEC_AGENT_ROLES, REQUIRED_SUPERSPEC_WORKFLOW_SKILLS, REVIEW_GUIDANCE_ROLES, REVIEW_EVIDENCE_REQUIRED_FIELDS, TDD_MODES, VERIFY_EVIDENCE_REQUIRED_FIELDS, allow, block, isObject, reason, renderList, repr, pinned_ref_key, safe_within, sha256_text, runtime, toPosix, } from "./util.js";
|
|
4
|
-
import { all_done, artifact_status_map, get_repo_root, is_done, normalize_gate, openspec_cli_probe } from "./openspec.js";
|
|
3
|
+
import { ARTIFACT_ENTER_GATE, MAIN_ADJUDICATION_DECISIONS, REQUEST_CHANGES_ROUTES, NO_TDD_REASONS, OPENSPEC_ARTIFACTS, FINAL_VERIFICATION_ROLES, REQUIRED_SUPERSPEC_AGENT_ROLES, REQUIRED_SUPERSPEC_WORKFLOW_SKILLS, REVIEW_GUIDANCE_ROLES, REVIEW_EVIDENCE_REQUIRED_FIELDS, TDD_MODES, VERIFY_EVIDENCE_REQUIRED_FIELDS, allow, block, isObject, reason, renderList, repr, pinned_ref_key, safe_within, sha256_text, runtime, toPosix, PENDING_CONFIRMATION_RE, UNCHECKED_CHECKBOX_RE, CHECKED_CHECKBOX_RE, } from "./util.js";
|
|
4
|
+
import { all_done, artifact_status_map, effective_superseded_ids, get_repo_root, is_done, normalize_gate, openspec_cli_probe } from "./openspec.js";
|
|
5
5
|
import { read_agent_toml_name, read_skill_frontmatter_name, sidecar_business_invariants_path, sidecar_discovery_path, sidecar_test_contract_path } from "./paths.js";
|
|
6
6
|
import { hookInitReasons } from "./hooks/health.js";
|
|
7
7
|
import { business_invariant_ids, business_invariant_validation_reasons, automated_hard_business_invariant_ids, evidence_invariant_refs, evidence_invariant_ref_reasons, evidence_test_contract_invariant_reasons, human_confirmation_business_invariant_ids, invariant_matrix_coverage_reasons, post_implementation_business_invariant_ids, red_green_invariant_ids, test_contract_invariant_ids, } from "./invariants.js";
|
|
8
|
-
import { evidence_test_id_reasons, declared_test_evidence_reasons, parse_spec_scenarios, parse_tasks, parse_test_contract_ids, parse_test_contract_records, red_green_test_ids, splitList, tasks_structure_hash, task_alternative_verification, task_test_evidence, task_test_refs, test_contract_covers_scenario, test_contract_invariant_refs_by_test, write_scope_conflict_reasons, } from "./tasks.js";
|
|
8
|
+
import { evidence_test_id_reasons, declared_test_evidence_reasons, parse_spec_scenarios, parse_tasks, parse_test_contract_ids, parse_test_contract_records, red_green_test_ids, splitList, task_apply_execution_surface, task_apply_execution_surface_reasons, tasks_structure_hash, task_alternative_verification, task_test_evidence, task_test_refs, test_contract_covers_scenario, test_contract_invariant_refs_by_test, write_scope_conflict_reasons, } from "./tasks.js";
|
|
9
9
|
import { duplicate_evidence_id_reasons, dangling_evidence_ref_reasons, final_verification_evidences, live_task_reopens, live_task_reopen_resolutions, live_pass, live_user_confirmations, pass_task_reopens, supersede_reasons, unresolved_live_task_reopens, validate_evidence_schema, verify_reference_reasons, } from "./evidence.js";
|
|
10
10
|
import { review_disclosure_reasons } from "./disclosure.js";
|
|
11
11
|
import { archive_manifest_path } from "./archive.js";
|
|
12
|
-
import {
|
|
13
|
-
import { apply_worker_chain_lifecycle_reasons as shared_apply_worker_chain_lifecycle_reasons, apply_worker_chain_lifecycle_state,
|
|
12
|
+
import { pre_edit_worker_test_run_reasons, } from "./apply_worker_chain.js";
|
|
13
|
+
import { apply_worker_chain_lifecycle_reasons as shared_apply_worker_chain_lifecycle_reasons, apply_worker_chain_lifecycle_state, } from "./apply_worker_chain_lifecycle.js";
|
|
14
14
|
function action_list(...items) {
|
|
15
15
|
return [...new Set(items.filter((item) => typeof item === "string" && item.length > 0))];
|
|
16
16
|
}
|
|
@@ -398,8 +398,15 @@ export function openspec_cli_capability_reasons() {
|
|
|
398
398
|
return [reason("openspec_native_surface_missing", probe.message)];
|
|
399
399
|
}
|
|
400
400
|
export function evidence_schema_guard(change, changeRoot, repoRoot, evidences) {
|
|
401
|
+
const effectiveDead = effective_superseded_ids(evidences);
|
|
401
402
|
return [
|
|
402
|
-
...evidences.flatMap((ev) =>
|
|
403
|
+
...evidences.flatMap((ev) => {
|
|
404
|
+
const problems = validate_evidence_schema(ev, change, changeRoot, repoRoot);
|
|
405
|
+
const id = typeof ev.evidence_id === "string" ? ev.evidence_id : "";
|
|
406
|
+
if (!id || !effectiveDead.has(id) || !ev.agent_role)
|
|
407
|
+
return problems;
|
|
408
|
+
return problems.filter((item) => item.code !== "stale_review");
|
|
409
|
+
}),
|
|
403
410
|
...duplicate_evidence_id_reasons(evidences),
|
|
404
411
|
...dangling_evidence_ref_reasons(evidences),
|
|
405
412
|
...supersede_reasons(evidences),
|
|
@@ -450,6 +457,54 @@ function stale_artifact_review_reasons(reviews, changeRoot, artifactRel, code, g
|
|
|
450
457
|
}
|
|
451
458
|
return reasons;
|
|
452
459
|
}
|
|
460
|
+
// B1: count unresolved open questions inside discovery.md's "待确认问题" section before explore_complete
|
|
461
|
+
// can pass. The section is located by a heading whose text carries confirmation semantics
|
|
462
|
+
// (待确认 / 需要用户确认 / open question / pending confirmation), tolerating inserted words such as
|
|
463
|
+
// 「用户」between 需要 and 确认 (the real secondment-hour-unit-support incident title was
|
|
464
|
+
// 「## 需要用户确认的问题」, which a naive contiguous 「需要确认」 substring would miss).
|
|
465
|
+
//
|
|
466
|
+
// Within a matched section, a list item ("- " / "* " / "+ " / "1. ") counts as open if it carries an
|
|
467
|
+
// unchecked checkbox ("[ ]", literal half-width space — full-width U+3000 is not treated as a box,
|
|
468
|
+
// matching GitHub task-list semantics) OR pending-confirmation wording (PENDING_CONFIRMATION_RE,
|
|
469
|
+
// shared with the B3 confirmation guard). An item is closed (not counted) if it begins with 「已确认」
|
|
470
|
+
// after its list marker or carries a checked box "[x]". Plain prose bullets with neither signal are
|
|
471
|
+
// ignored. When the section is absent the count is 0 (grandfathered: legacy changes with no such
|
|
472
|
+
// section are not retroactively blocked, matching this repo's treatment of legacy evidence).
|
|
473
|
+
const OPEN_QUESTION_SECTION_RE = /(待|需要|需)[^\n]{0,6}确认|待澄清|开放问题|open question|pending confirmation/iu;
|
|
474
|
+
const OPEN_QUESTION_LIST_ITEM_RE = /^\s*(?:[-*+]|\d+\.)\s+(.*)$/u;
|
|
475
|
+
function classify_open_question_line(line) {
|
|
476
|
+
const m = line.match(OPEN_QUESTION_LIST_ITEM_RE);
|
|
477
|
+
if (!m)
|
|
478
|
+
return "none";
|
|
479
|
+
const body = m[1].trimStart();
|
|
480
|
+
if (CHECKED_CHECKBOX_RE.test(line))
|
|
481
|
+
return "closed";
|
|
482
|
+
if (UNCHECKED_CHECKBOX_RE.test(line))
|
|
483
|
+
return "open";
|
|
484
|
+
if (/^已确认/u.test(body))
|
|
485
|
+
return "closed";
|
|
486
|
+
if (PENDING_CONFIRMATION_RE.test(line))
|
|
487
|
+
return "open";
|
|
488
|
+
return "none";
|
|
489
|
+
}
|
|
490
|
+
export function discovery_open_question_count(discoveryPath) {
|
|
491
|
+
if (!existsSync(discoveryPath) || !statSync(discoveryPath).isFile())
|
|
492
|
+
return 0;
|
|
493
|
+
const text = readFileSync(discoveryPath, "utf8");
|
|
494
|
+
if (!text.trim())
|
|
495
|
+
return 0;
|
|
496
|
+
let inSection = false;
|
|
497
|
+
let count = 0;
|
|
498
|
+
for (const line of text.split(/\r?\n/u)) {
|
|
499
|
+
if (/^#{1,6}\s/u.test(line)) {
|
|
500
|
+
inSection = OPEN_QUESTION_SECTION_RE.test(line);
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (inSection && classify_open_question_line(line) === "open")
|
|
504
|
+
count += 1;
|
|
505
|
+
}
|
|
506
|
+
return count;
|
|
507
|
+
}
|
|
453
508
|
export function check_superspec_gate(change, status, changeRoot, evidences, gateRaw) {
|
|
454
509
|
const gate = normalize_gate(gateRaw);
|
|
455
510
|
const amap = artifact_status_map(status);
|
|
@@ -470,6 +525,13 @@ export function check_superspec_gate(change, status, changeRoot, evidences, gate
|
|
|
470
525
|
// DISC Phase 1: material findings raised by explore reviews must be disclosed to the user
|
|
471
526
|
// (main_review_digest + user_review_decision) before the gate can pass.
|
|
472
527
|
reasons.push(...review_disclosure_reasons("explore_complete", changeRoot, evidences));
|
|
528
|
+
// B1: discovery.md must not still carry unresolved open questions. Without this the gate is a
|
|
529
|
+
// presence-check on human_confirmation and will pass even when discovery lists 「仍需确认」items
|
|
530
|
+
// (real incident: secondment-hour-unit-support entered propose with 8 open questions).
|
|
531
|
+
const openQuestionCount = discovery_open_question_count(discovery);
|
|
532
|
+
if (openQuestionCount > 0) {
|
|
533
|
+
reasons.push(reason("explore_open_questions_unresolved", `${discovery}: discovery.md 待确认问题段仍有 ${openQuestionCount} 条未勾选/「仍需确认」项,逐条与用户确认并改为已确认后才能进入 propose`));
|
|
534
|
+
}
|
|
473
535
|
}
|
|
474
536
|
else if (gate === "proposal_reviewed") {
|
|
475
537
|
// DISC Phase 2: proposal review is an internal gate, not an advisory note. It is born inside
|
|
@@ -626,6 +688,9 @@ export function check_superspec_gate(change, status, changeRoot, evidences, gate
|
|
|
626
688
|
if (Object.keys(tasks).length === 0)
|
|
627
689
|
reasons.push(reason("invalid_task_graph", "tasks.md has no structured tasks"));
|
|
628
690
|
reasons.push(...write_scope_conflict_reasons(tasks));
|
|
691
|
+
for (const [taskId, task] of Object.entries(tasks)) {
|
|
692
|
+
reasons.push(...task_apply_execution_surface_reasons(taskId, task));
|
|
693
|
+
}
|
|
629
694
|
// DISC Phase 3: tasks_complete enters the disclosure loop only once round-tagged review
|
|
630
695
|
// evidence appears (design: no mandatory role review on this gate until then).
|
|
631
696
|
reasons.push(...review_disclosure_reasons("tasks_complete", changeRoot, evidences));
|
|
@@ -702,6 +767,19 @@ function task_reopen_history(evidences, taskId) {
|
|
|
702
767
|
function task_reopen_match_key(ev) {
|
|
703
768
|
return `${String(ev.evidence_id ?? "")}\u0000${String(ev.reopen_id ?? "")}\u0000${String(ev.task_id ?? "")}`;
|
|
704
769
|
}
|
|
770
|
+
function worker_pre_edit_test_evidence(changeRoot, evidences, taskId, semanticStatus) {
|
|
771
|
+
const candidates = task_test_evidence(evidences, taskId, semanticStatus, "task_edit");
|
|
772
|
+
const valid = [];
|
|
773
|
+
const invalidReasons = [];
|
|
774
|
+
for (const ev of candidates) {
|
|
775
|
+
const problems = pre_edit_worker_test_run_reasons(changeRoot, ev, taskId, "pre_edit_test_run_invalid");
|
|
776
|
+
if (problems.length === 0)
|
|
777
|
+
valid.push(ev);
|
|
778
|
+
else
|
|
779
|
+
invalidReasons.push(...problems);
|
|
780
|
+
}
|
|
781
|
+
return { valid, invalid_reasons: invalidReasons, candidate_count: candidates.length };
|
|
782
|
+
}
|
|
705
783
|
function matching_reopen_resolutions(evidences, reopen) {
|
|
706
784
|
const key = task_reopen_match_key(reopen);
|
|
707
785
|
return live_task_reopen_resolutions(evidences)
|
|
@@ -1118,18 +1196,24 @@ export function check_task_edit(change, status, changeRoot, evidences, taskId) {
|
|
|
1118
1196
|
reasons.push(reason("invalid_no_tdd_reason", `task ${taskId}: no_tdd_reason=${repr(nr)}`));
|
|
1119
1197
|
}
|
|
1120
1198
|
else if (tddMode === "behavior-preserving-refactor") {
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1199
|
+
const characterizationProof = worker_pre_edit_test_evidence(changeRoot, evidences, taskId, "expected_success");
|
|
1200
|
+
const characterization = characterizationProof.valid;
|
|
1201
|
+
if (characterization.length === 0) {
|
|
1202
|
+
reasons.push(reason(characterizationProof.candidate_count === 0 ? "missing_characterization" : "missing_worker_characterization", `task ${taskId} (behavior-preserving-refactor) needs test-runner characterization evidence before edit`));
|
|
1203
|
+
reasons.push(...characterizationProof.invalid_reasons);
|
|
1204
|
+
}
|
|
1124
1205
|
reasons.push(...evidence_test_id_reasons(characterization, taskId, declared, contractIds, "characterization"));
|
|
1125
1206
|
reasons.push(...declared_test_evidence_reasons(characterization, taskId, declared, "characterization"));
|
|
1126
1207
|
reasons.push(...evidence_invariant_ref_reasons(characterization, taskId, declaredInvariants, validInvariantIds, "characterization"));
|
|
1127
1208
|
reasons.push(...evidence_test_contract_invariant_reasons(characterization, taskId, test_contract_invariant_refs_by_test(changeRoot), "characterization"));
|
|
1128
1209
|
}
|
|
1129
1210
|
else {
|
|
1130
|
-
const
|
|
1131
|
-
|
|
1132
|
-
|
|
1211
|
+
const redProof = worker_pre_edit_test_evidence(changeRoot, evidences, taskId, "expected_failure");
|
|
1212
|
+
const red = redProof.valid;
|
|
1213
|
+
if (red.length === 0) {
|
|
1214
|
+
reasons.push(reason(redProof.candidate_count === 0 ? "missing_red_evidence" : "missing_worker_red_evidence", `task ${taskId} requires test-runner RED evidence before edit`));
|
|
1215
|
+
reasons.push(...redProof.invalid_reasons);
|
|
1216
|
+
}
|
|
1133
1217
|
reasons.push(...evidence_test_id_reasons(red, taskId, declared, contractIds, "RED"));
|
|
1134
1218
|
reasons.push(...declared_test_evidence_reasons(red, taskId, declared, "RED"));
|
|
1135
1219
|
reasons.push(...evidence_invariant_ref_reasons(red, taskId, declaredInvariants, validInvariantIds, "RED"));
|
|
@@ -1149,6 +1233,34 @@ export function check_task_edit(change, status, changeRoot, evidences, taskId) {
|
|
|
1149
1233
|
export function apply_worker_chain_lifecycle_reasons(repoRoot, changeRoot, evidences, taskId) {
|
|
1150
1234
|
return shared_apply_worker_chain_lifecycle_reasons(repoRoot, changeRoot, evidences, taskId);
|
|
1151
1235
|
}
|
|
1236
|
+
function apply_worker_chain_ref_ids(ev, singular, plural) {
|
|
1237
|
+
const ids = new Set();
|
|
1238
|
+
if (Array.isArray(ev[plural])) {
|
|
1239
|
+
for (const item of ev[plural]) {
|
|
1240
|
+
if (isObject(item) && typeof item.evidence_id === "string" && item.evidence_id)
|
|
1241
|
+
ids.add(item.evidence_id);
|
|
1242
|
+
else if (typeof item === "string" && item)
|
|
1243
|
+
ids.add(item);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
const single = ev[singular];
|
|
1247
|
+
if (isObject(single) && typeof single.evidence_id === "string" && single.evidence_id)
|
|
1248
|
+
ids.add(single.evidence_id);
|
|
1249
|
+
else if (typeof single === "string" && single)
|
|
1250
|
+
ids.add(single);
|
|
1251
|
+
return [...ids].sort();
|
|
1252
|
+
}
|
|
1253
|
+
function active_no_tdd_metadata_matches(active, executionSurface, noTddReason) {
|
|
1254
|
+
const preIds = Array.isArray(active?.pre_edit_evidence_refs) ? active.pre_edit_evidence_refs.map(String).filter(Boolean) : [];
|
|
1255
|
+
return isObject(active)
|
|
1256
|
+
&& active.pre_edit_proof_kind === "no_tdd_declared"
|
|
1257
|
+
&& preIds.length === 0
|
|
1258
|
+
&& active.tdd_required === false
|
|
1259
|
+
&& active.apply_execution_surface === executionSurface
|
|
1260
|
+
&& typeof noTddReason === "string"
|
|
1261
|
+
&& active.no_tdd_reason === noTddReason
|
|
1262
|
+
&& NO_TDD_REASONS.has(noTddReason);
|
|
1263
|
+
}
|
|
1152
1264
|
export function check_task_complete(change, status, changeRoot, evidences, taskId) {
|
|
1153
1265
|
const gate = "task_complete";
|
|
1154
1266
|
const reasons = [];
|
|
@@ -1163,6 +1275,7 @@ export function check_task_complete(change, status, changeRoot, evidences, taskI
|
|
|
1163
1275
|
const task = parse_tasks(changeRoot)[taskId];
|
|
1164
1276
|
if (!task)
|
|
1165
1277
|
return block(change, gate, [reason("unknown_task", `task ${taskId} not found`)], { task_id: taskId });
|
|
1278
|
+
reasons.push(...task_apply_execution_surface_reasons(taskId, task));
|
|
1166
1279
|
const workerChainState = apply_worker_chain_lifecycle_state(get_repo_root(status), changeRoot, evidences, taskId);
|
|
1167
1280
|
reasons.push(...workerChainState.completionReasons);
|
|
1168
1281
|
const reopenHistory = task_reopen_history(evidences, taskId);
|
|
@@ -1185,21 +1298,11 @@ export function check_task_complete(change, status, changeRoot, evidences, taskI
|
|
|
1185
1298
|
reasons.push(reason("invalid_tdd_mode", `task ${taskId}: tdd_mode=${repr(tddMode)}`));
|
|
1186
1299
|
if (tddRequired) {
|
|
1187
1300
|
const allGreen = task_test_evidence(evidences, taskId, "expected_success", "task_complete");
|
|
1188
|
-
const green =
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
&& workerChainState.validClosedGreenEvidenceIds.has(String(ev.evidence_id ?? ""))))
|
|
1192
|
-
: workerChainState.takeoverBaselines.length > 0
|
|
1193
|
-
? allGreen.filter((ev) => serial_completion_green(ev) && workerChainState.takeoverBaselines.some((baseline) => (baseline.successorGreenRefs.includes(String(ev.evidence_id ?? ""))
|
|
1194
|
-
&& fingerprint_matches(ev.implementation_fingerprint, baseline.fingerprint))))
|
|
1195
|
-
: allGreen.filter(serial_completion_green);
|
|
1301
|
+
const green = allGreen.filter((ev) => (ev.apply_execution_chain === "executor_worker"
|
|
1302
|
+
&& workerChainState.validClosedChainIds.has(String(ev.apply_worker_chain_id ?? ""))
|
|
1303
|
+
&& workerChainState.validClosedGreenEvidenceIds.has(String(ev.evidence_id ?? ""))));
|
|
1196
1304
|
if (green.length === 0)
|
|
1197
1305
|
reasons.push(reason("missing_green_evidence", `task ${taskId} requires GREEN evidence (expected_success) before completion`));
|
|
1198
|
-
if (workerChainState.validClosedGreenEvidenceIds.size === 0 && workerChainState.takeoverBaselines.length > 0) {
|
|
1199
|
-
if (green.length === 0) {
|
|
1200
|
-
reasons.push(reason("apply_worker_chain_takeover_green_mismatch", `task ${taskId} requires declared successor serial GREEN evidence after takeover baseline with matching implementation fingerprint`));
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
1306
|
const declared = new Set(splitList(attrs.test_refs ?? ""));
|
|
1204
1307
|
const declaredInvariants = new Set(splitList(attrs.invariant_refs ?? ""));
|
|
1205
1308
|
if (declared.size === 0)
|
|
@@ -1235,9 +1338,39 @@ export function check_task_complete(change, status, changeRoot, evidences, taskI
|
|
|
1235
1338
|
const nr = attrs.no_tdd_reason;
|
|
1236
1339
|
if (!NO_TDD_REASONS.has(nr))
|
|
1237
1340
|
reasons.push(reason("invalid_no_tdd_reason", `task ${taskId}: no_tdd_reason=${repr(nr)}`));
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1341
|
+
const executionSurface = task_apply_execution_surface(task);
|
|
1342
|
+
const directAlternativeAllowed = executionSurface === "docs_generated" || executionSurface === "no_code";
|
|
1343
|
+
const implementationAlternativeRequired = executionSurface === "implementation" || executionSurface === "runtime_config";
|
|
1344
|
+
const allVerifications = task_alternative_verification(evidences, taskId);
|
|
1345
|
+
let validAlternativeEvidenceIds = workerChainState.validClosedAlternativeEvidenceIds;
|
|
1346
|
+
if (implementationAlternativeRequired) {
|
|
1347
|
+
const activeByChain = new Map(live_pass(evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId })
|
|
1348
|
+
.filter((ev) => ev.chain_state === "active" && typeof ev.apply_worker_chain_id === "string")
|
|
1349
|
+
.map((ev) => [String(ev.apply_worker_chain_id), ev]));
|
|
1350
|
+
const closedAlternativeChains = live_pass(evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId })
|
|
1351
|
+
.filter((ev) => (ev.chain_state === "closed"
|
|
1352
|
+
&& String(ev.completion_proof_kind ?? "green_tests") === "alternative_verification"
|
|
1353
|
+
&& workerChainState.validClosedAlternativeChainIds.has(String(ev.apply_worker_chain_id ?? ""))));
|
|
1354
|
+
const matchingClosedAlternativeChains = closedAlternativeChains.filter((ev) => (active_no_tdd_metadata_matches(activeByChain.get(String(ev.apply_worker_chain_id ?? "")), executionSurface, nr)));
|
|
1355
|
+
const metadataMismatchChains = closedAlternativeChains
|
|
1356
|
+
.filter((ev) => !matchingClosedAlternativeChains.includes(ev))
|
|
1357
|
+
.map((ev) => String(ev.apply_worker_chain_id ?? ev.evidence_id ?? ""))
|
|
1358
|
+
.filter(Boolean)
|
|
1359
|
+
.sort();
|
|
1360
|
+
if (metadataMismatchChains.length > 0) {
|
|
1361
|
+
reasons.push(reason("apply_worker_chain_active_invalid", `task ${taskId} closed alternative apply_worker_chain active no-TDD metadata must match task metadata: ${renderList(metadataMismatchChains)}`, metadataMismatchChains));
|
|
1362
|
+
}
|
|
1363
|
+
validAlternativeEvidenceIds = new Set(matchingClosedAlternativeChains.flatMap((ev) => apply_worker_chain_ref_ids(ev, "alternative_verification_evidence_ref", "alternative_verification_evidence_refs")));
|
|
1364
|
+
}
|
|
1365
|
+
const verifications = directAlternativeAllowed
|
|
1366
|
+
? allVerifications
|
|
1367
|
+
: allVerifications.filter((ev) => validAlternativeEvidenceIds.has(String(ev.evidence_id ?? "")));
|
|
1368
|
+
if (implementationAlternativeRequired && verifications.length === 0) {
|
|
1369
|
+
reasons.push(reason("missing_alternative_verification", `task ${taskId} apply_execution_surface=${executionSurface} requires closed apply_worker_chain alternative_verification proof before completion`));
|
|
1370
|
+
}
|
|
1371
|
+
else if (!implementationAlternativeRequired && verifications.length === 0) {
|
|
1240
1372
|
reasons.push(reason("missing_alternative_verification", `task ${taskId} has tdd_required:false and needs alternative verification evidence`));
|
|
1373
|
+
}
|
|
1241
1374
|
if (reopenMode.mode === "reopened") {
|
|
1242
1375
|
const reopen = reopenMode.reopen;
|
|
1243
1376
|
const reopenId = String(reopen.reopen_id ?? "");
|
|
@@ -224,11 +224,32 @@ function postToolUseOutput(decision) {
|
|
|
224
224
|
},
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
|
+
function isSubagentHookEvent(eventName) {
|
|
228
|
+
return eventName === "SubagentStart" || eventName === "SubagentStop";
|
|
229
|
+
}
|
|
227
230
|
function subagentOutput(decision, eventName) {
|
|
231
|
+
if (!decision.allowed) {
|
|
232
|
+
return {
|
|
233
|
+
systemMessage: `SuperSpec ${eventName} runlog skipped; ${decision.block_reasons.map((item) => item.message).join("; ") || "audit telemetry unavailable"}`,
|
|
234
|
+
hookSpecificOutput: {
|
|
235
|
+
hookEventName: eventName,
|
|
236
|
+
additionalContext: "SuperSpec subagent telemetry is best-effort and did not block the workflow.",
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
228
240
|
return {
|
|
229
241
|
systemMessage: `SuperSpec ${eventName} runlog recorded as ${decision.trust}; strict_profile=${decision.strict_profile}`,
|
|
230
242
|
};
|
|
231
243
|
}
|
|
244
|
+
function inertSubagentOutput(eventName, message) {
|
|
245
|
+
return {
|
|
246
|
+
systemMessage: message,
|
|
247
|
+
hookSpecificOutput: {
|
|
248
|
+
hookEventName: eventName,
|
|
249
|
+
additionalContext: "SuperSpec subagent telemetry is best-effort and did not block the workflow.",
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
232
253
|
function writeTempEvent(event) {
|
|
233
254
|
const path = join(tmpdir(), `superspec-hook-event-${randomUUID()}.json`);
|
|
234
255
|
writeFileSync(path, `${JSON.stringify(event)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
@@ -241,8 +262,9 @@ function validateHookEvent(event) {
|
|
|
241
262
|
if (!["PreToolUse", "PostToolUse", "SubagentStart", "SubagentStop"].includes(eventName)) {
|
|
242
263
|
return `unsupported hook_event_name: ${eventName}`;
|
|
243
264
|
}
|
|
244
|
-
if (typeof event.cwd !== "string" || !event.cwd)
|
|
265
|
+
if ((eventName === "PreToolUse" || eventName === "PostToolUse") && (typeof event.cwd !== "string" || !event.cwd)) {
|
|
245
266
|
return "hook event missing required cwd";
|
|
267
|
+
}
|
|
246
268
|
if (eventName === "PreToolUse") {
|
|
247
269
|
if (typeof event.tool_name !== "string" || !event.tool_name)
|
|
248
270
|
return "PreToolUse hook event missing required tool_name";
|
|
@@ -254,6 +276,7 @@ function validateHookEvent(event) {
|
|
|
254
276
|
export function runHookAdapter(argv = process.argv.slice(2), stdin = readFileSync(0, "utf8")) {
|
|
255
277
|
const parsedArgs = parseArgs(argv);
|
|
256
278
|
let event;
|
|
279
|
+
let eventNameForFailure = "";
|
|
257
280
|
try {
|
|
258
281
|
if (!stdin.trim())
|
|
259
282
|
throw new Error("hook stdin is empty");
|
|
@@ -261,11 +284,16 @@ export function runHookAdapter(argv = process.argv.slice(2), stdin = readFileSyn
|
|
|
261
284
|
if (!isObject(parsed))
|
|
262
285
|
throw new Error("hook stdin must be a JSON object");
|
|
263
286
|
event = parsed;
|
|
287
|
+
eventNameForFailure = typeof event.hook_event_name === "string" ? event.hook_event_name : "";
|
|
264
288
|
const validationError = validateHookEvent(event);
|
|
265
289
|
if (validationError)
|
|
266
290
|
throw new Error(validationError);
|
|
267
291
|
}
|
|
268
292
|
catch (err) {
|
|
293
|
+
if (isSubagentHookEvent(eventNameForFailure)) {
|
|
294
|
+
const payload = inertSubagentOutput(eventNameForFailure, `SuperSpec ${eventNameForFailure} runlog skipped: ${err.message}`);
|
|
295
|
+
return { code: 0, stdout: `${JSON.stringify(payload)}\n`, stderr: "" };
|
|
296
|
+
}
|
|
269
297
|
return { code: 2, stdout: "", stderr: `SuperSpec hook event parse failed: ${err.message}\n` };
|
|
270
298
|
}
|
|
271
299
|
const inference = parsedArgs.change ? { state: "unique", change: parsedArgs.change } : inferChangeFromActiveSessions(event);
|
|
@@ -274,8 +302,9 @@ export function runHookAdapter(argv = process.argv.slice(2), stdin = readFileSyn
|
|
|
274
302
|
const payload = noChangeFallback(event, inference);
|
|
275
303
|
return { code: 0, stdout: `${JSON.stringify(payload)}\n`, stderr: "" };
|
|
276
304
|
}
|
|
277
|
-
|
|
305
|
+
let eventRef = null;
|
|
278
306
|
try {
|
|
307
|
+
eventRef = writeTempEvent(event);
|
|
279
308
|
const eventName = String(event.hook_event_name ?? "");
|
|
280
309
|
let payload;
|
|
281
310
|
if (eventName === "PreToolUse") {
|
|
@@ -298,14 +327,21 @@ export function runHookAdapter(argv = process.argv.slice(2), stdin = readFileSyn
|
|
|
298
327
|
return { code: 0, stdout: `${JSON.stringify(payload)}\n`, stderr: "" };
|
|
299
328
|
}
|
|
300
329
|
catch (err) {
|
|
330
|
+
if (isSubagentHookEvent(String(event.hook_event_name ?? ""))) {
|
|
331
|
+
const eventName = String(event.hook_event_name ?? "");
|
|
332
|
+
const payload = inertSubagentOutput(eventName, `SuperSpec ${eventName} runlog skipped: ${err.message}`);
|
|
333
|
+
return { code: 0, stdout: `${JSON.stringify(payload)}\n`, stderr: "" };
|
|
334
|
+
}
|
|
301
335
|
return { code: 2, stdout: "", stderr: `SuperSpec hook failed closed: ${err.message}\n` };
|
|
302
336
|
}
|
|
303
337
|
finally {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
338
|
+
if (eventRef) {
|
|
339
|
+
try {
|
|
340
|
+
unlinkSync(resolve(eventRef));
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
// best effort temp cleanup
|
|
344
|
+
}
|
|
309
345
|
}
|
|
310
346
|
}
|
|
311
347
|
}
|
|
@@ -457,7 +457,7 @@ export function hookCheckWrite(change, eventRef) {
|
|
|
457
457
|
block_reasons: blockReasons,
|
|
458
458
|
audit_only_reasons: auditOnlyReasons,
|
|
459
459
|
target_paths: intent.target_paths,
|
|
460
|
-
next_allowed_actions: ["rerun the relevant superspec
|
|
460
|
+
next_allowed_actions: ["rerun the relevant superspec check and use the expected workflow command surface"],
|
|
461
461
|
});
|
|
462
462
|
}
|
|
463
463
|
return hookDecision(ctx, "hook_check_write", true, {
|
|
@@ -510,7 +510,7 @@ export function hookCheckWrite(change, eventRef) {
|
|
|
510
510
|
block_reasons: blockReasons,
|
|
511
511
|
audit_only_reasons: auditOnlyReasons,
|
|
512
512
|
target_paths: intent.target_paths,
|
|
513
|
-
next_allowed_actions: ["rerun the relevant superspec
|
|
513
|
+
next_allowed_actions: ["rerun the relevant superspec check and use the expected workflow command surface"],
|
|
514
514
|
});
|
|
515
515
|
}
|
|
516
516
|
return hookDecision(ctx, "hook_check_write", true, {
|
package/dist/src/hooks/health.js
CHANGED
|
@@ -4,18 +4,6 @@ import { reason, sha256_file } from "../util.js";
|
|
|
4
4
|
import { HOOK_ADAPTER_VERSION } from "./types.js";
|
|
5
5
|
const HOOK_COMMAND = 'superspec-hook --change "$SUPERSPEC_CHANGE"';
|
|
6
6
|
const EXPECTED_HOOK_MATRIX = [
|
|
7
|
-
{
|
|
8
|
-
eventName: "PreToolUse",
|
|
9
|
-
matcher: "Bash|apply_patch|Edit|Write|mcp__.*",
|
|
10
|
-
timeout: 120,
|
|
11
|
-
statusMessage: "SuperSpec 写入策略检查",
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
eventName: "PostToolUse",
|
|
15
|
-
matcher: "Bash",
|
|
16
|
-
timeout: 120,
|
|
17
|
-
statusMessage: "SuperSpec 运行证据记录",
|
|
18
|
-
},
|
|
19
7
|
{
|
|
20
8
|
eventName: "SubagentStart",
|
|
21
9
|
matcher: ".*",
|
|
@@ -2340,7 +2340,9 @@ function tokenAtSameCommand(tokens, idx) {
|
|
|
2340
2340
|
return tokens[idx];
|
|
2341
2341
|
}
|
|
2342
2342
|
function isHookEntrypointName(name) {
|
|
2343
|
-
return name === "superspec-
|
|
2343
|
+
return name === "superspec-check"
|
|
2344
|
+
|| name === "superspec-check.js"
|
|
2345
|
+
|| name === "superspec-guard"
|
|
2344
2346
|
|| name === "superspec-guard.js"
|
|
2345
2347
|
|| name === "superspec-hook"
|
|
2346
2348
|
|| name === "superspec-hook.js";
|
|
@@ -2392,9 +2394,13 @@ function commandLooksLikeInternalHookWriter(command, depth = 0) {
|
|
|
2392
2394
|
return true;
|
|
2393
2395
|
if (next === "guard" && afterNext?.startsWith("hook-record-"))
|
|
2394
2396
|
return true;
|
|
2397
|
+
if (next === "check" && afterNext?.startsWith("hook-record-"))
|
|
2398
|
+
return true;
|
|
2395
2399
|
}
|
|
2396
2400
|
if (name === "superspec-guard" && tokenAtSameCommand(tokens, commandIdx + 1)?.startsWith("hook-record-"))
|
|
2397
2401
|
return true;
|
|
2402
|
+
if (name === "superspec-check" && tokenAtSameCommand(tokens, commandIdx + 1)?.startsWith("hook-record-"))
|
|
2403
|
+
return true;
|
|
2398
2404
|
if (isHookAdapterEntrypointName(name))
|
|
2399
2405
|
return true;
|
|
2400
2406
|
if (isHookEntrypointName(name) && tokenAtSameCommand(tokens, commandIdx + 1)?.startsWith("hook-record-"))
|
|
@@ -2442,6 +2448,10 @@ function commandLooksLikeUnsafeLifecycleTermination(command, depth = 0) {
|
|
|
2442
2448
|
if (segmentHasDangerousReason(commandIdx + 3, segmentEnd))
|
|
2443
2449
|
return true;
|
|
2444
2450
|
}
|
|
2451
|
+
if (token === "superspec" && tokens[commandIdx + 1] === "check" && tokens[commandIdx + 2] === "hook-session-end") {
|
|
2452
|
+
if (segmentHasDangerousReason(commandIdx + 3, segmentEnd))
|
|
2453
|
+
return true;
|
|
2454
|
+
}
|
|
2445
2455
|
if (token === "superspec" && tokens[commandIdx + 1] === "hook-session-end") {
|
|
2446
2456
|
if (segmentHasDangerousReason(commandIdx + 2, segmentEnd))
|
|
2447
2457
|
return true;
|
package/dist/src/i18n.js
CHANGED
|
@@ -73,6 +73,7 @@ const REASON_ZH = {
|
|
|
73
73
|
invalid_tdd_mode: { label_zh: "TDD 模式无效", hint_zh: "task 上声明的 tdd_mode 不在允许枚举里。" },
|
|
74
74
|
invalid_no_tdd_reason: { label_zh: "免 TDD 理由无效", hint_zh: "task 上声明的 no_tdd_reason 不在允许枚举里。" },
|
|
75
75
|
missing_discovery: { label_zh: "缺少探索记录", hint_zh: "探索阶段缺少 discovery.md,或者该文件内容为空。" },
|
|
76
|
+
explore_open_questions_unresolved: { label_zh: "探索待确认问题未清空", hint_zh: "discovery.md 的待确认问题还有未勾选或「仍需确认」的项,需要逐条与用户确认并改成已确认后才能进入 propose。" },
|
|
76
77
|
missing_proposal: { label_zh: "缺少提案工件", hint_zh: "提案工件还没有完成。" },
|
|
77
78
|
missing_proposal_review: { label_zh: "缺少提案审查", hint_zh: "提案审查阶段还没有收集到严格审查证据。" },
|
|
78
79
|
explore_complete_failed: { label_zh: "探索阶段未通过", hint_zh: "后续阶段依赖的探索完成检查还没有通过。" },
|
|
@@ -95,6 +96,65 @@ const REASON_ZH = {
|
|
|
95
96
|
user_uninstall_failed: { label_zh: "用户级卸载失败", hint_zh: "用户级 SuperSpec 卸载失败。" },
|
|
96
97
|
guard_error: { label_zh: "检查执行失败", hint_zh: "guard 命令执行过程中发生了可报告错误。" },
|
|
97
98
|
guard_internal_error: { label_zh: "检查器内部错误", hint_zh: "guard 内部抛出了异常,需要查看错误信息排查。" },
|
|
99
|
+
"apply_worker_chain_active": { label_zh: "活跃工作链存在", hint_zh: "当前任务已有活跃的 apply worker chain。" },
|
|
100
|
+
"apply_worker_chain_active_conflict": { label_zh: "活跃工作链冲突", hint_zh: "存在多条活跃 apply worker chain,需要先收敛到一条。" },
|
|
101
|
+
"apply_worker_chain_missing_active": { label_zh: "缺少活跃工作链", hint_zh: "当前任务没有活跃的 apply worker chain,需要先启动。" },
|
|
102
|
+
"apply_worker_chain_terminal_conflict": { label_zh: "工作链终态冲突", hint_zh: "存在多条终态 apply worker chain,需要先收敛。" },
|
|
103
|
+
"archive_not_found": { label_zh: "归档未找到", hint_zh: "没有找到已归档的 change,确认归档是否完成。" },
|
|
104
|
+
"bash_write_target_unknown": { label_zh: "Bash 写入目标未知", hint_zh: "Bash 命令的写入目标无法识别,需要明确目标路径。" },
|
|
105
|
+
"curl_config_write_target_unknown": { label_zh: "curl 配置写入目标未知", hint_zh: "curl 配置类命令的写入目标无法识别。" },
|
|
106
|
+
"curl_write_target_unknown": { label_zh: "curl 写入目标未知", hint_zh: "curl 命令的写入目标无法识别。" },
|
|
107
|
+
"custom_superspec_schema_present": { label_zh: "自定义 schema 存在", hint_zh: "检测到自定义 OpenSpec schema,SuperSpec 只支持默认 spec-driven。" },
|
|
108
|
+
"dangling_evidence_ref": { label_zh: "证据引用悬空", hint_zh: "evidence 引用的 evidence_id 不存在,检查引用或恢复被删证据。" },
|
|
109
|
+
"dirty_worktree_unattributed": { label_zh: "脏工作区未归属", hint_zh: "工作区有改动但无法归因到当前 change,需要先确认归属。" },
|
|
110
|
+
"duplicate_test_command": { label_zh: "测试命令重复", hint_zh: "检测到重复的测试命令声明。" },
|
|
111
|
+
"evidence_bad_status": { label_zh: "证据状态非法", hint_zh: "evidence 的 status 字段不在合法枚举内。" },
|
|
112
|
+
"evidence_forbidden_field": { label_zh: "证据含禁止字段", hint_zh: "evidence 包含当前 kind 不允许的字段。" },
|
|
113
|
+
"evidence_missing_field": { label_zh: "证据缺字段", hint_zh: "evidence 缺少必需字段。" },
|
|
114
|
+
"evidence_unknown_kind": { label_zh: "证据 kind 未知", hint_zh: "evidence 的 kind 不在白名单内。" },
|
|
115
|
+
"evidence_unparsable": { label_zh: "证据不可解析", hint_zh: "evidence 文件不是合法 JSON。" },
|
|
116
|
+
"evidence_unsafe_ref": { label_zh: "证据引用不安全", hint_zh: "evidence 的引用路径逃逸了允许范围。" },
|
|
117
|
+
"forbidden_alias_path": { label_zh: "禁用别名路径", hint_zh: "使用了被禁用的配置或状态文件别名。" },
|
|
118
|
+
"hook_internal_writer_invocation": { label_zh: "内部写入命令被调用", hint_zh: "模型控制的工具调用不得触发 SuperSpec hook 写入命令。" },
|
|
119
|
+
"hook_manifest_missing_or_unmanaged": { label_zh: "hook manifest 缺失或不受管", hint_zh: ".codex/hooks.json 缺失或不是 SuperSpec 受管版本。" },
|
|
120
|
+
"hook_manifest_unmanaged": { label_zh: "hook manifest 不受管", hint_zh: ".codex/hooks.json 不是 SuperSpec 受管版本。" },
|
|
121
|
+
"hook_session_audit_only": { label_zh: "hook 会话仅审计", hint_zh: "当前 hook 会话是 audit-only,不能作为可信强制来源。" },
|
|
122
|
+
"hook_session_corrupt": { label_zh: "hook 会话损坏", hint_zh: "活跃 hook 会话记录损坏,需要重建。" },
|
|
123
|
+
"hook_session_expired": { label_zh: "hook 会话过期", hint_zh: "活跃 hook 会话已过期。" },
|
|
124
|
+
"hook_session_termination_untrusted": { label_zh: "hook 会话终止不可信", hint_zh: "会话终止来源不可信,不能关闭 lease。" },
|
|
125
|
+
"invariants_not_reviewed": { label_zh: "业务约束未审查", hint_zh: "business-invariants 尚未通过审查。" },
|
|
126
|
+
"mixed_request_changes_route": { label_zh: "混合返工路由", hint_zh: "request_changes 同时包含不一致的 route。" },
|
|
127
|
+
"openspec_canonical_direct_edit": { label_zh: "直接编辑 OpenSpec 正本", hint_zh: "不允许绕过 openspec instructions 直接编辑正本 artifact。" },
|
|
128
|
+
"openspec_status_incompatible": { label_zh: "OpenSpec 状态不兼容", hint_zh: "openspec status 输出与预期结构不一致。" },
|
|
129
|
+
"openspec_tasks_direct_edit": { label_zh: "直接编辑 tasks", hint_zh: "不允许绕过流程直接编辑 tasks.md。" },
|
|
130
|
+
"post_implementation_invariant_backfill": { label_zh: "实现后补不变量", hint_zh: "不允许在实现后才补业务不变量。" },
|
|
131
|
+
"protected_path_dirty": { label_zh: "受保护路径被改动", hint_zh: "受保护路径处于脏状态。" },
|
|
132
|
+
"protected_trust_root_link_source": { label_zh: "信任根符号链接", hint_zh: "信任根路径通过符号链接指向外部来源。" },
|
|
133
|
+
"protected_trust_root_write": { label_zh: "写入信任根", hint_zh: "不允许写入 SuperSpec 信任根路径。" },
|
|
134
|
+
"ref_empty": { label_zh: "引用为空", hint_zh: "引用字段为空字符串。" },
|
|
135
|
+
"ref_not_readable": { label_zh: "引用不可读", hint_zh: "引用指向的文件不可读。" },
|
|
136
|
+
"ref_path_unsafe": { label_zh: "引用路径不安全", hint_zh: "引用路径逃逸了允许的根。" },
|
|
137
|
+
"reopen_lifecycle_exhausted": { label_zh: "重开次数耗尽", hint_zh: "task reopen 的生命周期次数已用尽。" },
|
|
138
|
+
"required_claim_unadjudicated": { label_zh: "必需 claim 未裁决", hint_zh: "存在尚未裁决的必需 claim。" },
|
|
139
|
+
"review_diff_not_covered": { label_zh: "review 未覆盖 diff", hint_zh: "review 未覆盖当前 diff。" },
|
|
140
|
+
"runtime_evidence_missing_command_fingerprint": { label_zh: "运行时证据缺命令指纹", hint_zh: "runtime evidence 缺少 command fingerprint。" },
|
|
141
|
+
"runtime_evidence_missing_exit_code": { label_zh: "运行时证据缺退出码", hint_zh: "runtime evidence 缺少 exit code。" },
|
|
142
|
+
"runtime_evidence_missing_hook_event": { label_zh: "运行时证据缺 hook 事件", hint_zh: "runtime evidence 缺少可信 hook 事件。" },
|
|
143
|
+
"runtime_evidence_missing_raw_log_pin": { label_zh: "运行时证据缺日志锚定", hint_zh: "runtime evidence 缺少 raw log pinned refs。" },
|
|
144
|
+
"runtime_evidence_missing_token": { label_zh: "运行时证据缺 token", hint_zh: "runtime evidence 缺少绑定 token。" },
|
|
145
|
+
"runtime_evidence_untrusted_provenance": { label_zh: "运行时证据来源不可信", hint_zh: "runtime evidence 缺少可信 provenance。" },
|
|
146
|
+
"scope_expansion_requires_propose": { label_zh: "范围扩大需回 propose", hint_zh: "任务范围扩大需要回 propose 重新确认。" },
|
|
147
|
+
"self_review_not_allowed": { label_zh: "不允许自审", hint_zh: "主线程不能充当审查角色。" },
|
|
148
|
+
"shell_path_may_touch_trust_root": { label_zh: "shell 路径可能触及信任根", hint_zh: "shell 展开的路径可能触及 SuperSpec 信任根。" },
|
|
149
|
+
"superspec_not_preserved": { label_zh: "SuperSpec 记录未保全", hint_zh: "归档后 .superspec 记录未能从 preservation manifest 追溯。" },
|
|
150
|
+
"target_path_outside_repo": { label_zh: "写入目标在仓库外", hint_zh: "写入目标路径逃逸了仓库根。" },
|
|
151
|
+
"task_already_done": { label_zh: "任务已完成", hint_zh: "目标 task 已经勾选完成。" },
|
|
152
|
+
"test_id_not_in_log": { label_zh: "测试 id 不在日志中", hint_zh: "声称的 test_id 未出现在引用日志里。" },
|
|
153
|
+
"test_not_declared_for_task": { label_zh: "测试未为任务声明", hint_zh: "测试未在任务的 test_refs 中声明。" },
|
|
154
|
+
"untrusted_test_command_ref": { label_zh: "测试命令引用不可信", hint_zh: "测试命令引用来源不可信。" },
|
|
155
|
+
"unsafe_write_scope": { label_zh: "写范围不安全", hint_zh: "任务的 write_scope 包含不安全路径。" },
|
|
156
|
+
"unsupported_write_surface": { label_zh: "不支持的写入面", hint_zh: "当前面不支持该写入操作。" },
|
|
157
|
+
"write_scope_conflict": { label_zh: "写范围冲突", hint_zh: "多个任务的 write_scope 存在冲突。" },
|
|
98
158
|
};
|
|
99
159
|
const WORKFLOW_TERMS_ZH = {
|
|
100
160
|
"check-enter": { term: "check-enter", label_zh: "进入阶段前检查", hint_zh: "进入某个流程阶段前先运行的检查命令。" },
|
|
@@ -110,9 +170,9 @@ const TRUST_WARNING_ZH = {
|
|
|
110
170
|
"project init installs project surfaces only; change sidecars are created lazily by later superspec phases": "project init 只安装项目级内容;change sidecar 会在后续 superspec 阶段按需创建。",
|
|
111
171
|
};
|
|
112
172
|
const ACTION_ZH_EXACT = {
|
|
113
|
-
"continue with superspec-explore/propose/apply
|
|
114
|
-
"create/select a change during superspec-explore, then run change-scoped
|
|
115
|
-
"review *.new files for user-modified surfaces, then rerun superspec
|
|
173
|
+
"continue with superspec-explore/propose/apply check checks": "继续执行探索、提案和实现阶段的前置检查。",
|
|
174
|
+
"create/select a change during superspec-explore, then run change-scoped check checks": "先在探索阶段创建或选择一个变更,然后运行该变更范围内的前置检查。",
|
|
175
|
+
"review *.new files for user-modified surfaces, then rerun superspec check check-init": "先检查因用户改动而生成的 *.new 文件,然后重新运行初始化前置检查;Windows PowerShell 中使用 superspec.cmd check check-init。",
|
|
116
176
|
"review *.new files for user-modified user-level surfaces": "先检查用户级内容对应的 *.new 文件。",
|
|
117
177
|
"user-level SuperSpec Codex surfaces installed; use superspec init --scope project inside a repo when project-local surfaces are needed": "用户级 SuperSpec 配套内容已安装;如果仓库里还需要项目级内容,请在仓库内运行 superspec init --scope project;Windows PowerShell 中使用 superspec.cmd init --scope project。",
|
|
118
178
|
"finish remaining unchecked tasks and mark them complete only after check-task-complete passes": "先完成剩余未勾选任务,并且仅在任务完成检查通过后再标记完成。",
|
package/dist/src/init_cli.js
CHANGED
|
@@ -318,7 +318,7 @@ function run_init(args, scope) {
|
|
|
318
318
|
}
|
|
319
319
|
else if (args.mode === "update") {
|
|
320
320
|
summary = engineDecision(`${gatePrefix}_update`, targetRoot, update_workflow(targetRoot, { scope }), [
|
|
321
|
-
scope === "project" ? "review *.new files for user-modified surfaces, then rerun superspec
|
|
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
323
|
}
|
|
324
324
|
else if (scope === "user") {
|