@haposoft/cafekit 0.16.7 → 0.18.1
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 +14 -22
- package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
- package/bin/__tests__/codex-hooks.test.js +169 -272
- package/bin/__tests__/codex-native.test.js +100 -202
- package/bin/__tests__/develop-contract.test.js +122 -1413
- package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
- package/bin/__tests__/grok-envelope.test.js +0 -56
- package/bin/__tests__/hook-payload.test.js +4 -6
- package/bin/__tests__/legacy-authority-decoupled.test.js +154 -0
- package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
- package/bin/__tests__/omp-bridge.test.js +20 -17
- package/bin/__tests__/omp-hooks.test.js +0 -19
- package/bin/__tests__/optional-skill-inventory.test.js +112 -4
- package/bin/__tests__/orca-session.test.js +0 -2
- package/bin/__tests__/package-inventory.test.js +90 -925
- package/bin/__tests__/runtime-dir.test.js +0 -3
- package/bin/__tests__/runtime-schema.test.js +3 -2
- package/bin/__tests__/skill-routing-source.test.js +27 -15
- package/bin/__tests__/spec-narrowing.test.js +30 -161
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -437
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- package/bin/__tests__/ui-ux-skill.test.js +101 -0
- package/bin/__tests__/usage-hook-retired.test.js +114 -0
- package/bin/__tests__/validator-grounding.test.js +18 -2949
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/codex-runtime.js +27 -0
- package/bin/phases/copy-payload.js +13 -28
- package/bin/phases/omp-runtime.js +25 -0
- package/package.json +2 -3
- package/src/claude/CLAUDE.md +3 -10
- package/src/claude/agents/brainstormer.md +7 -3
- package/src/claude/agents/code-auditor.md +44 -56
- package/src/claude/agents/debugger.md +8 -88
- package/src/claude/agents/deployer.md +3 -5
- package/src/claude/agents/docs-keeper.md +6 -14
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +12 -16
- package/src/claude/agents/inspector.md +4 -3
- package/src/claude/agents/project-manager.md +9 -17
- package/src/claude/agents/researcher.md +9 -3
- package/src/claude/agents/spec-maker.md +15 -15
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +244 -333
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +160 -0
- package/src/claude/hooks/__tests__/state.test.js +68 -0
- package/src/claude/hooks/__tests__/statusline.test.js +140 -11
- package/src/claude/hooks/agent.cjs +2 -1
- package/src/claude/hooks/lib/hook-payload.cjs +2 -2
- package/src/claude/hooks/rules.cjs +2 -1
- package/src/claude/hooks/session.cjs +0 -1
- package/src/claude/hooks/spec-gate.cjs +17 -83
- package/src/claude/hooks/spec-state.cjs +142 -39
- package/src/claude/hooks/state.cjs +21 -2
- package/src/claude/migration-manifest.json +42 -22
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +2 -3
- package/src/claude/rules/hook-protocols.md +8 -15
- package/src/claude/rules/manage-docs.md +9 -23
- package/src/claude/rules/orchestrator.md +3 -4
- package/src/claude/rules/review-audit-self-decision.md +3 -3
- package/src/claude/rules/skill-domain-routing.md +1 -3
- package/src/claude/rules/skill-workflow-routing.md +4 -5
- package/src/claude/rules/state-sync.md +3 -12
- package/src/claude/rules/workflow.md +11 -19
- package/src/claude/runtime.schema.json +5 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
- package/src/claude/scripts/provenance.cjs +23 -5
- package/src/claude/scripts/spec-receipt.cjs +24 -121
- package/src/claude/scripts/spec-resolver.cjs +41 -458
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/settings/settings.json +1 -32
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/{question → ask}/SKILL.md +9 -75
- package/src/claude/skills/brainstorm/SKILL.md +43 -131
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +29 -81
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -8
- package/src/claude/skills/debug/SKILL.md +29 -149
- package/src/claude/skills/develop/SKILL.md +26 -83
- package/src/claude/skills/develop/references/parallel-waves.md +0 -8
- package/src/claude/skills/develop/references/quality-gate.md +15 -13
- package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
- package/src/claude/skills/docs/SKILL.md +2 -2
- package/src/claude/skills/docs/references/update-workflow.md +1 -1
- package/src/claude/skills/fix/SKILL.md +153 -0
- package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
- package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
- package/src/claude/skills/git/SKILL.md +48 -9
- package/src/claude/skills/git/references/commit-protocols.md +35 -16
- package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
- package/src/claude/skills/orca/SKILL.md +9 -8
- package/src/claude/skills/research/SKILL.md +17 -46
- package/src/claude/skills/scout/SKILL.md +61 -0
- package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
- package/src/claude/skills/specs/SKILL.md +22 -29
- package/src/claude/skills/specs/references/review.md +8 -6
- package/src/claude/skills/specs/references/templates.md +34 -41
- package/src/claude/skills/sync/SKILL.md +16 -45
- package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
- package/src/claude/skills/sync/references/sync-protocols.md +4 -13
- package/src/claude/skills/test/SKILL.md +39 -104
- package/src/claude/skills/test/references/execution-strategy.md +93 -21
- package/src/claude/skills/test/references/failure-triage.md +1 -1
- package/src/claude/skills/ui-ux/LICENSE +21 -0
- package/src/claude/skills/ui-ux/SKILL.md +497 -0
- package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
- package/src/claude/skills/ui-ux/references/budgets.md +113 -0
- package/src/claude/skills/ui-ux/references/checklist.md +271 -0
- package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
- package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
- package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
- package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
- package/src/claude/skills/ui-ux/references/components/button.md +142 -0
- package/src/claude/skills/ui-ux/references/components/card.md +113 -0
- package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
- package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
- package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
- package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
- package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
- package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
- package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
- package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
- package/src/claude/skills/ui-ux/references/components/input.md +161 -0
- package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
- package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
- package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
- package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
- package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
- package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
- package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
- package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
- package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
- package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
- package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
- package/src/claude/skills/ui-ux/references/design-process.md +650 -0
- package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
- package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
- package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
- package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
- package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
- package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
- package/src/claude/skills/ui-ux/references/principles.md +408 -0
- package/src/claude/skills/ui-ux/references/refactor.md +245 -0
- package/src/claude/skills/ui-ux/references/responsive.md +195 -0
- package/src/claude/skills/ui-ux/references/review.md +528 -0
- package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
- package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
- package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
- package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
- package/src/claude/skills/ui-ux/references/styles.md +311 -0
- package/src/claude/skills/ui-ux/references/system.md +219 -0
- package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
- package/src/claude/skills/ui-ux/references/tokens.css +362 -0
- package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
- package/src/claude/skills/ui-ux/scripts/package.json +9 -0
- package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +25 -80
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- package/src/claude/status.cjs +149 -144
- package/src/codex/AGENTS.md +8 -13
- package/src/codex/hooks/agent.cjs +2 -1
- package/src/codex/hooks/lib/spec-receipt.cjs +4 -32
- package/src/codex/hooks/lib/spec-utils.cjs +6 -52
- package/src/codex/hooks/rules.cjs +2 -1
- package/src/codex/hooks/session.cjs +0 -2
- package/src/codex/hooks/spec-gate.cjs +13 -81
- package/src/codex/hooks/spec-state.cjs +37 -41
- package/src/codex/hooks.json +0 -20
- package/src/codex/rules/state-sync.md +3 -12
- package/src/common/AGENTS.md +6 -12
- package/src/omp/extensions/cafekit-bridge.mjs +6 -6
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
- package/src/claude/hooks/completion-authority-check.cjs +0 -56
- package/src/claude/hooks/completion-authority-state.cjs +0 -527
- package/src/claude/hooks/completion-authority.cjs +0 -163
- package/src/claude/hooks/lib/context.cjs +0 -616
- package/src/claude/hooks/lib/detect.cjs +0 -474
- package/src/claude/hooks/semantic-review-authority.cjs +0 -154
- package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
- package/src/claude/hooks/usage.cjs +0 -197
- package/src/claude/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3941
- package/src/claude/skills/code-review/references/verification-gate.md +0 -46
- package/src/claude/skills/delegate/SKILL.md +0 -124
- package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
- package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
- package/src/claude/skills/hotfix/SKILL.md +0 -289
- package/src/claude/skills/inspect/SKILL.md +0 -142
- package/src/claude/skills/loop/SKILL.md +0 -114
- package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
- package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
- package/src/claude/skills/route/SKILL.md +0 -94
- package/src/claude/skills/route/references/agent-timing.md +0 -46
- package/src/claude/skills/route/references/chaining-patterns.md +0 -53
- package/src/claude/skills/route/references/task-taxonomy.md +0 -48
- package/src/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- package/src/claude/skills/specs/templates/task.md +0 -47
- package/src/codex/hooks/completion-authority-check.cjs +0 -67
- package/src/codex/hooks/completion-authority-state.cjs +0 -527
- package/src/codex/hooks/completion-authority.cjs +0 -109
- package/src/codex/hooks/semantic-review-authority.cjs +0 -130
- package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
- /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const crypto = require('crypto');
|
|
7
|
-
const SEMANTIC = require('./spec-semantic-model.cjs');
|
|
8
|
-
const VALIDATOR = require('./validate-spec-output.cjs');
|
|
9
|
-
const { canonicalProjectRoot, groundSpec } = require('./spec-ground.cjs');
|
|
10
|
-
|
|
11
|
-
const REVIEW_RESULT_FIELDS = Object.freeze([
|
|
12
|
-
'verdict', 'findings', 'unresolved_decisions', 'graph_coverage',
|
|
13
|
-
'reviewed_criteria', 'counterexamples', 'reviewer_evidence',
|
|
14
|
-
]);
|
|
15
|
-
const C2_RECEIPT_FIELDS = Object.freeze([
|
|
16
|
-
'status', 'semantic_digest', 'verdict', 'lifecycle_disposition',
|
|
17
|
-
'findings', 'unresolved_decisions', 'graph_coverage', 'repair_round',
|
|
18
|
-
'reviewed_criteria', 'counterexamples', 'reviewer_evidence',
|
|
19
|
-
]);
|
|
20
|
-
const HISTORY_FIELDS = Object.freeze(['lineage_id', 'entries']);
|
|
21
|
-
const HISTORY_ENTRY_FIELDS = Object.freeze([
|
|
22
|
-
'sequence', 'semantic_digest', 'review_receipt_digest', 'verdict',
|
|
23
|
-
'lifecycle_disposition', 'blocking_count', 'attempt_index', 'review_epoch',
|
|
24
|
-
]);
|
|
25
|
-
const SHA256_RE = /^sha256:[0-9a-f]{64}$/;
|
|
26
|
-
|
|
27
|
-
function plain(value) {
|
|
28
|
-
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function exactKeys(value, fields) {
|
|
32
|
-
return plain(value)
|
|
33
|
-
&& JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...fields].sort());
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function stableDigest(value, semantic) {
|
|
37
|
-
if (!semantic || typeof semantic.stableJson !== 'function') {
|
|
38
|
-
throw new Error('semantic stableJson is required for readiness receipts');
|
|
39
|
-
}
|
|
40
|
-
const stable = semantic.stableJson(value);
|
|
41
|
-
return `sha256:${crypto.createHash('sha256').update(Buffer.from(stable, 'utf8')).digest('hex')}`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function requireString(value, label, minimum = 1) {
|
|
45
|
-
if (typeof value !== 'string' || value.length < minimum) {
|
|
46
|
-
throw new Error(`${label} must be a string with at least ${minimum} characters`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function validateReviewFinding(finding, index) {
|
|
51
|
-
if (!exactKeys(finding, ['id', 'severity', 'location', 'summary', 'blocking', 'disposition'])) {
|
|
52
|
-
throw new Error(`semantic review finding ${index} fields must be exact`);
|
|
53
|
-
}
|
|
54
|
-
requireString(finding.id, `semantic review finding ${index}.id`);
|
|
55
|
-
requireString(finding.location, `semantic review finding ${index}.location`);
|
|
56
|
-
requireString(finding.summary, `semantic review finding ${index}.summary`, 8);
|
|
57
|
-
if (!['Critical', 'High', 'Medium', 'Low'].includes(finding.severity)) {
|
|
58
|
-
throw new Error(`semantic review finding ${index}.severity is invalid`);
|
|
59
|
-
}
|
|
60
|
-
if (typeof finding.blocking !== 'boolean') {
|
|
61
|
-
throw new Error(`semantic review finding ${index}.blocking must be boolean`);
|
|
62
|
-
}
|
|
63
|
-
if (!['open', 'accepted', 'mitigated'].includes(finding.disposition)) {
|
|
64
|
-
throw new Error(`semantic review finding ${index}.disposition is invalid`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function validateReviewDecision(decision, index) {
|
|
69
|
-
if (!exactKeys(decision, ['id', 'summary', 'blocking'])) {
|
|
70
|
-
throw new Error(`semantic review unresolved decision ${index} fields must be exact`);
|
|
71
|
-
}
|
|
72
|
-
requireString(decision.id, `semantic review unresolved decision ${index}.id`);
|
|
73
|
-
requireString(decision.summary, `semantic review unresolved decision ${index}.summary`, 8);
|
|
74
|
-
if (typeof decision.blocking !== 'boolean') {
|
|
75
|
-
throw new Error(`semantic review unresolved decision ${index}.blocking must be boolean`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function validateGraphCoverage(surface, index) {
|
|
80
|
-
if (!exactKeys(surface, ['surface', 'covered', 'notes'])) {
|
|
81
|
-
throw new Error(`semantic review graph coverage ${index} fields must be exact`);
|
|
82
|
-
}
|
|
83
|
-
if (![
|
|
84
|
-
'criterion_local', 'cross_criterion', 'runtime_path',
|
|
85
|
-
'assumption_provenance', 'compatibility_migration',
|
|
86
|
-
].includes(surface.surface)) {
|
|
87
|
-
throw new Error(`semantic review graph coverage ${index}.surface is invalid`);
|
|
88
|
-
}
|
|
89
|
-
if (typeof surface.covered !== 'boolean') {
|
|
90
|
-
throw new Error(`semantic review graph coverage ${index}.covered must be boolean`);
|
|
91
|
-
}
|
|
92
|
-
requireString(surface.notes, `semantic review graph coverage ${index}.notes`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function validateCounterexample(counterexample, index) {
|
|
96
|
-
if (!exactKeys(counterexample, [
|
|
97
|
-
'criterion', 'case_kind', 'scenario', 'expected', 'decision_refs', 'verification_ref',
|
|
98
|
-
])) {
|
|
99
|
-
throw new Error(`semantic review counterexample ${index} fields must be exact`);
|
|
100
|
-
}
|
|
101
|
-
for (const field of ['criterion', 'case_kind', 'scenario', 'expected', 'verification_ref']) {
|
|
102
|
-
requireString(counterexample[field], `semantic review counterexample ${index}.${field}`);
|
|
103
|
-
}
|
|
104
|
-
if (!Array.isArray(counterexample.decision_refs)
|
|
105
|
-
|| counterexample.decision_refs.some((value) => typeof value !== 'string')) {
|
|
106
|
-
throw new Error(`semantic review counterexample ${index}.decision_refs must be string array`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function validateReviewerEvidence(evidence) {
|
|
111
|
-
if (evidence === null) return;
|
|
112
|
-
if (!exactKeys(evidence, ['assurance', 'summary'])) {
|
|
113
|
-
throw new Error('semantic review reviewer_evidence fields must be exact');
|
|
114
|
-
}
|
|
115
|
-
if (!['Routine', 'Elevated', 'Strict'].includes(evidence.assurance)) {
|
|
116
|
-
throw new Error('semantic review reviewer_evidence.assurance is invalid');
|
|
117
|
-
}
|
|
118
|
-
requireString(evidence.summary, 'semantic review reviewer_evidence.summary', 8);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function readReviewResult(input) {
|
|
122
|
-
if (!exactKeys(input, REVIEW_RESULT_FIELDS)) {
|
|
123
|
-
throw new Error(`semantic review result fields must be exactly ${REVIEW_RESULT_FIELDS.join(', ')}`);
|
|
124
|
-
}
|
|
125
|
-
if (input.verdict !== 'PASS' && input.verdict !== 'FAIL') {
|
|
126
|
-
throw new Error('semantic review result verdict must be PASS or FAIL');
|
|
127
|
-
}
|
|
128
|
-
if (!Array.isArray(input.findings)) throw new Error('semantic review result findings must be an array');
|
|
129
|
-
input.findings.forEach(validateReviewFinding);
|
|
130
|
-
if (!Array.isArray(input.unresolved_decisions)) {
|
|
131
|
-
throw new Error('semantic review result unresolved_decisions must be an array');
|
|
132
|
-
}
|
|
133
|
-
input.unresolved_decisions.forEach(validateReviewDecision);
|
|
134
|
-
if (!Array.isArray(input.graph_coverage)) {
|
|
135
|
-
throw new Error('semantic review result graph_coverage must be an array');
|
|
136
|
-
}
|
|
137
|
-
input.graph_coverage.forEach(validateGraphCoverage);
|
|
138
|
-
if (!Array.isArray(input.reviewed_criteria)
|
|
139
|
-
|| input.reviewed_criteria.some((value) => typeof value !== 'string' || !/^R\d+\.\d+$/.test(value))
|
|
140
|
-
|| new Set(input.reviewed_criteria).size !== input.reviewed_criteria.length) {
|
|
141
|
-
throw new Error('semantic review result reviewed_criteria must be unique RN.M strings');
|
|
142
|
-
}
|
|
143
|
-
if (!Array.isArray(input.counterexamples)) {
|
|
144
|
-
throw new Error('semantic review result counterexamples must be an array');
|
|
145
|
-
}
|
|
146
|
-
input.counterexamples.forEach(validateCounterexample);
|
|
147
|
-
validateReviewerEvidence(input.reviewer_evidence);
|
|
148
|
-
return JSON.parse(JSON.stringify(input));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function blockingCount(review) {
|
|
152
|
-
return review.findings.filter((finding) => finding.blocking).length
|
|
153
|
-
+ review.unresolved_decisions.filter((decision) => decision.blocking).length;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function buildCompletedReceipt(review, semanticDigest, recordedEntry) {
|
|
157
|
-
return {
|
|
158
|
-
status: 'completed',
|
|
159
|
-
semantic_digest: semanticDigest,
|
|
160
|
-
verdict: review.verdict,
|
|
161
|
-
lifecycle_disposition: recordedEntry.lifecycle_disposition,
|
|
162
|
-
findings: JSON.parse(JSON.stringify(review.findings)),
|
|
163
|
-
unresolved_decisions: JSON.parse(JSON.stringify(review.unresolved_decisions)),
|
|
164
|
-
graph_coverage: JSON.parse(JSON.stringify(review.graph_coverage)),
|
|
165
|
-
repair_round: recordedEntry.attempt_index,
|
|
166
|
-
reviewed_criteria: JSON.parse(JSON.stringify(review.reviewed_criteria)),
|
|
167
|
-
counterexamples: JSON.parse(JSON.stringify(review.counterexamples)),
|
|
168
|
-
reviewer_evidence: JSON.parse(JSON.stringify(review.reviewer_evidence)),
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function validateHistory(history) {
|
|
173
|
-
if (!plain(history) || !exactKeys(history, HISTORY_FIELDS)) {
|
|
174
|
-
throw new Error('spec.validation.semantic_review_history must contain exactly lineage_id and entries');
|
|
175
|
-
}
|
|
176
|
-
if (typeof history.lineage_id !== 'string' || !SHA256_RE.test(history.lineage_id)) {
|
|
177
|
-
throw new Error('spec.validation.semantic_review_history.lineage_id must be a sha256 digest');
|
|
178
|
-
}
|
|
179
|
-
if (!Array.isArray(history.entries)) {
|
|
180
|
-
throw new Error('spec.validation.semantic_review_history.entries must be an array');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const receipts = new Set();
|
|
184
|
-
let priorPasses = 0;
|
|
185
|
-
let failuresSincePass = 0;
|
|
186
|
-
let blocked = false;
|
|
187
|
-
for (const [index, entry] of history.entries.entries()) {
|
|
188
|
-
if (!plain(entry) || !exactKeys(entry, HISTORY_ENTRY_FIELDS)) {
|
|
189
|
-
throw new Error(`semantic review history entry ${index} fields diverge from the canonical entry`);
|
|
190
|
-
}
|
|
191
|
-
if (entry.sequence !== index) throw new Error(`semantic review history entry ${index} has a divergent sequence`);
|
|
192
|
-
if (typeof entry.semantic_digest !== 'string' || !SHA256_RE.test(entry.semantic_digest)) {
|
|
193
|
-
throw new Error(`semantic review history entry ${index} has an invalid semantic digest`);
|
|
194
|
-
}
|
|
195
|
-
if (typeof entry.review_receipt_digest !== 'string' || !SHA256_RE.test(entry.review_receipt_digest)) {
|
|
196
|
-
throw new Error(`semantic review history entry ${index} has an invalid review receipt digest`);
|
|
197
|
-
}
|
|
198
|
-
if (receipts.has(entry.review_receipt_digest)) {
|
|
199
|
-
throw new Error(`semantic review history entry ${index} duplicates a review receipt digest`);
|
|
200
|
-
}
|
|
201
|
-
receipts.add(entry.review_receipt_digest);
|
|
202
|
-
if (entry.verdict !== 'PASS' && entry.verdict !== 'FAIL') {
|
|
203
|
-
throw new Error(`semantic review history entry ${index} has an invalid verdict`);
|
|
204
|
-
}
|
|
205
|
-
if (entry.review_epoch !== priorPasses) {
|
|
206
|
-
throw new Error(`semantic review history entry ${index} has a divergent review_epoch`);
|
|
207
|
-
}
|
|
208
|
-
if (entry.attempt_index !== failuresSincePass) {
|
|
209
|
-
throw new Error(`semantic review history entry ${index} has a divergent attempt_index`);
|
|
210
|
-
}
|
|
211
|
-
if (entry.attempt_index > 2) {
|
|
212
|
-
throw new Error(`semantic review history entry ${index} exceeds the terminal attempt limit`);
|
|
213
|
-
}
|
|
214
|
-
if (!Number.isInteger(entry.blocking_count) || entry.blocking_count < 0) {
|
|
215
|
-
throw new Error(`semantic review history entry ${index} has an invalid blocking_count`);
|
|
216
|
-
}
|
|
217
|
-
const expectedDisposition = entry.verdict === 'PASS' || entry.attempt_index < 2
|
|
218
|
-
? 'CONTINUE' : 'BLOCKED';
|
|
219
|
-
if (entry.lifecycle_disposition !== expectedDisposition) {
|
|
220
|
-
throw new Error(`semantic review history entry ${index} has a divergent lifecycle_disposition`);
|
|
221
|
-
}
|
|
222
|
-
if (blocked) throw new Error('semantic review history contains an entry after BLOCKED');
|
|
223
|
-
if (entry.verdict === 'PASS') {
|
|
224
|
-
if (entry.blocking_count !== 0) throw new Error(`semantic review history entry ${index} PASS has blockers`);
|
|
225
|
-
priorPasses += 1;
|
|
226
|
-
failuresSincePass = 0;
|
|
227
|
-
} else {
|
|
228
|
-
failuresSincePass += 1;
|
|
229
|
-
blocked = entry.lifecycle_disposition === 'BLOCKED';
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return { history: JSON.parse(JSON.stringify(history)), receipts, priorPasses, failuresSincePass, blocked };
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function deriveLineageId(spec, semantic) {
|
|
236
|
-
if (!plain(spec) || typeof spec.feature_name !== 'string' || spec.feature_name.length === 0) {
|
|
237
|
-
throw new Error('spec.feature_name is required for semantic review lineage');
|
|
238
|
-
}
|
|
239
|
-
if (typeof spec.created_at !== 'string' || spec.created_at.length === 0) {
|
|
240
|
-
throw new Error('spec.created_at is required for semantic review lineage');
|
|
241
|
-
}
|
|
242
|
-
return stableDigest({ feature_name: spec.feature_name, created_at: spec.created_at }, semantic);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function currentHistory(spec, semantic) {
|
|
246
|
-
const lineageId = deriveLineageId(spec, semantic);
|
|
247
|
-
const supplied = spec.validation?.semantic_review_history;
|
|
248
|
-
if (supplied === undefined || supplied === null) {
|
|
249
|
-
return {
|
|
250
|
-
history: { lineage_id: lineageId, entries: [] },
|
|
251
|
-
receipts: new Set(), priorPasses: 0, failuresSincePass: 0, blocked: false,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
const state = validateHistory(supplied);
|
|
255
|
-
if (state.history.lineage_id !== lineageId) {
|
|
256
|
-
throw new Error('spec.validation.semantic_review_history lineage_id diverges from spec identity');
|
|
257
|
-
}
|
|
258
|
-
return state;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function canonicalCheck({ directory, root, candidate, validator, grounder }) {
|
|
262
|
-
const validation = validator.validateSpec(directory, candidate);
|
|
263
|
-
if (validation.errors.length) throw new Error(`readiness validation failed (${validation.errors.join('; ')})`);
|
|
264
|
-
const grounding = grounder({ specDir: directory, root, spec: candidate });
|
|
265
|
-
if (grounding.errors.length) throw new Error(`readiness grounding failed (${grounding.errors.join('; ')})`);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function atomicReplace(file, bytes, io = fs) {
|
|
269
|
-
const stat = io.lstatSync(file);
|
|
270
|
-
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('spec.json must be a regular non-symlink file');
|
|
271
|
-
const original = io.readFileSync(file);
|
|
272
|
-
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.finalize-${process.pid}-${Date.now()}`);
|
|
273
|
-
let descriptor;
|
|
274
|
-
try {
|
|
275
|
-
descriptor = io.openSync(temporary, 'wx', stat.mode);
|
|
276
|
-
io.writeFileSync(descriptor, bytes);
|
|
277
|
-
io.fsyncSync(descriptor);
|
|
278
|
-
io.closeSync(descriptor);
|
|
279
|
-
descriptor = undefined;
|
|
280
|
-
if (process.env.CAFEKIT_FINALIZE_FAIL_BEFORE_RENAME === '1') throw new Error('injected finalization failure before rename');
|
|
281
|
-
io.renameSync(temporary, file);
|
|
282
|
-
} catch (error) {
|
|
283
|
-
if (descriptor !== undefined) try { io.closeSync(descriptor); } catch {}
|
|
284
|
-
try { io.unlinkSync(temporary); } catch {}
|
|
285
|
-
if (!io.readFileSync(file).equals(original)) {
|
|
286
|
-
const rollback = `${temporary}-rollback`;
|
|
287
|
-
try {
|
|
288
|
-
io.writeFileSync(rollback, original, { flag: 'wx', mode: stat.mode });
|
|
289
|
-
io.renameSync(rollback, file);
|
|
290
|
-
} finally { try { io.unlinkSync(rollback); } catch {} }
|
|
291
|
-
}
|
|
292
|
-
throw error;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function finalizeReadiness({
|
|
297
|
-
specDir,
|
|
298
|
-
reviewResult,
|
|
299
|
-
projectRoot,
|
|
300
|
-
io = fs,
|
|
301
|
-
validator = VALIDATOR,
|
|
302
|
-
grounder = groundSpec,
|
|
303
|
-
semantic = SEMANTIC,
|
|
304
|
-
now = new Date().toISOString(),
|
|
305
|
-
}) {
|
|
306
|
-
const directory = io.realpathSync(path.resolve(specDir));
|
|
307
|
-
const root = projectRoot ? io.realpathSync(path.resolve(projectRoot)) : canonicalProjectRoot(directory);
|
|
308
|
-
const relative = path.relative(root, directory);
|
|
309
|
-
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
310
|
-
throw new Error('spec directory escapes the canonical project root');
|
|
311
|
-
}
|
|
312
|
-
const specFile = path.join(directory, 'spec.json');
|
|
313
|
-
const original = io.readFileSync(specFile);
|
|
314
|
-
let spec;
|
|
315
|
-
try { spec = JSON.parse(original); } catch (error) { throw new Error(`spec.json is invalid JSON (${error.message})`); }
|
|
316
|
-
if (!plain(spec) || spec.schema_version !== '2.1') throw new Error('readiness finalization accepts canonical schema 2.1 only');
|
|
317
|
-
const review = readReviewResult(reviewResult);
|
|
318
|
-
const blockerCount = blockingCount(review);
|
|
319
|
-
if (review.verdict === 'PASS' && blockerCount > 0) {
|
|
320
|
-
throw new Error('semantic review PASS cannot contain blocking findings or unresolved decisions');
|
|
321
|
-
}
|
|
322
|
-
const state = currentHistory(spec, semantic);
|
|
323
|
-
const projection = semantic.modelFromMarkdown(directory, spec);
|
|
324
|
-
if (projection.errors.length) throw new Error(`semantic projection failed (${projection.errors.join('; ')})`);
|
|
325
|
-
|
|
326
|
-
const candidate = JSON.parse(JSON.stringify(spec));
|
|
327
|
-
candidate.semantic_model = projection.model;
|
|
328
|
-
if (!plain(candidate.validation)) throw new Error('spec.validation must be an object');
|
|
329
|
-
const authoringValidation = candidate.validation.authoring_validation;
|
|
330
|
-
candidate.validation = {
|
|
331
|
-
...candidate.validation,
|
|
332
|
-
status: 'completed',
|
|
333
|
-
semantic_review_history: {
|
|
334
|
-
lineage_id: state.history.lineage_id,
|
|
335
|
-
entries: [...state.history.entries],
|
|
336
|
-
},
|
|
337
|
-
};
|
|
338
|
-
if (JSON.stringify(candidate.validation.authoring_validation) !== JSON.stringify(authoringValidation)) {
|
|
339
|
-
throw new Error('spec.validation.authoring_validation must be preserved');
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
candidate.ready_for_implementation = false;
|
|
343
|
-
const digestResult = validator.computeSemanticDigest21(directory, candidate);
|
|
344
|
-
if (digestResult.errors.length) throw new Error(`semantic digest failed (${digestResult.errors.join('; ')})`);
|
|
345
|
-
for (const entry of state.history.entries) {
|
|
346
|
-
const hypotheticalReceipt = buildCompletedReceipt(review, digestResult.digest, entry);
|
|
347
|
-
const hypotheticalDigest = stableDigest(hypotheticalReceipt, semantic);
|
|
348
|
-
if (entry.review_receipt_digest === hypotheticalDigest) {
|
|
349
|
-
return {
|
|
350
|
-
spec,
|
|
351
|
-
semantic_digest: entry.semantic_digest,
|
|
352
|
-
replayed: true,
|
|
353
|
-
history_entry: entry,
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
if (state.blocked) throw new Error('readiness finalization refused: current review epoch already ends BLOCKED');
|
|
358
|
-
const reviewEpoch = state.priorPasses;
|
|
359
|
-
const attemptIndex = state.failuresSincePass;
|
|
360
|
-
if (attemptIndex > 2) throw new Error('readiness finalization refused: no next attempt exists');
|
|
361
|
-
const lifecycleDisposition = review.verdict === 'PASS' || attemptIndex < 2 ? 'CONTINUE' : 'BLOCKED';
|
|
362
|
-
const receipt = buildCompletedReceipt(review, digestResult.digest, {
|
|
363
|
-
attempt_index: attemptIndex,
|
|
364
|
-
lifecycle_disposition: lifecycleDisposition,
|
|
365
|
-
});
|
|
366
|
-
if (!exactKeys(receipt, C2_RECEIPT_FIELDS)) {
|
|
367
|
-
throw new Error('completed semantic review receipt fields diverge from C2');
|
|
368
|
-
}
|
|
369
|
-
candidate.validation.semantic_review = receipt;
|
|
370
|
-
const reviewReceiptDigest = stableDigest(receipt, semantic);
|
|
371
|
-
const historyEntry = {
|
|
372
|
-
sequence: state.history.entries.length,
|
|
373
|
-
semantic_digest: digestResult.digest,
|
|
374
|
-
review_receipt_digest: reviewReceiptDigest,
|
|
375
|
-
verdict: review.verdict,
|
|
376
|
-
lifecycle_disposition: lifecycleDisposition,
|
|
377
|
-
blocking_count: blockerCount,
|
|
378
|
-
attempt_index: attemptIndex,
|
|
379
|
-
review_epoch: reviewEpoch,
|
|
380
|
-
};
|
|
381
|
-
candidate.validation.semantic_review_history.entries.push(historyEntry);
|
|
382
|
-
candidate.updated_at = now;
|
|
383
|
-
canonicalCheck({ directory, root, candidate, validator, grounder });
|
|
384
|
-
|
|
385
|
-
const ready = review.verdict === 'PASS'
|
|
386
|
-
&& lifecycleDisposition === 'CONTINUE'
|
|
387
|
-
&& blockerCount === 0
|
|
388
|
-
&& candidate.validation.semantic_review.semantic_digest === digestResult.digest;
|
|
389
|
-
candidate.ready_for_implementation = ready;
|
|
390
|
-
if (ready) {
|
|
391
|
-
if (plain(candidate.timestamps)) {
|
|
392
|
-
candidate.timestamps = { ...candidate.timestamps, validation_done: now };
|
|
393
|
-
}
|
|
394
|
-
canonicalCheck({ directory, root, candidate, validator, grounder });
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const bytes = Buffer.from(`${JSON.stringify(candidate, null, 2)}\n`);
|
|
398
|
-
atomicReplace(specFile, bytes, io);
|
|
399
|
-
return { spec: candidate, semantic_digest: digestResult.digest, replayed: false, history_entry: historyEntry };
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function usage() {
|
|
403
|
-
process.stderr.write('Usage: node .claude/scripts/spec-readiness.cjs specs/<feature> --review-result <review.json>\n');
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function main() {
|
|
407
|
-
const args = process.argv.slice(2);
|
|
408
|
-
const marker = args.indexOf('--review-result');
|
|
409
|
-
if (args.length !== 3 || marker !== 1) { usage(); process.exitCode = 2; return; }
|
|
410
|
-
try {
|
|
411
|
-
const reviewFile = path.resolve(args[2]);
|
|
412
|
-
const reviewStat = fs.lstatSync(reviewFile);
|
|
413
|
-
if (!reviewStat.isFile() || reviewStat.isSymbolicLink()) throw new Error('review result must be a regular non-symlink JSON file');
|
|
414
|
-
const reviewResult = JSON.parse(fs.readFileSync(reviewFile, 'utf8'));
|
|
415
|
-
const result = finalizeReadiness({ specDir: args[0], reviewResult });
|
|
416
|
-
process.stdout.write(`SPEC_READY ${args[0]} ${result.semantic_digest}\n`);
|
|
417
|
-
} catch (error) {
|
|
418
|
-
process.stderr.write(`SPEC_NOT_READY ${error.message}\n`);
|
|
419
|
-
process.exitCode = 1;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
module.exports = { atomicReplace, finalizeReadiness, readReviewResult };
|
|
424
|
-
|
|
425
|
-
if (require.main === module) main();
|