@haposoft/cafekit 0.16.0-rc.6 → 0.16.0-rc.7

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.
Files changed (86) hide show
  1. package/README.md +21 -58
  2. package/bin/__tests__/change-firewall.semantic-firewall.test.js +4 -5
  3. package/bin/__tests__/codex-hooks.test.js +137 -0
  4. package/bin/__tests__/codex-native.test.js +130 -74
  5. package/bin/__tests__/develop-contract.test.js +155 -76
  6. package/bin/__tests__/installer-safety.test.js +19 -154
  7. package/bin/__tests__/package-inventory.test.js +10 -20
  8. package/bin/__tests__/release-preflight.semantic-firewall.test.js +2 -2
  9. package/bin/__tests__/validator-grounding.test.js +11 -11
  10. package/bin/install.js +2 -7
  11. package/bin/lib/context.js +0 -45
  12. package/bin/lib/copy-utils.js +1 -1
  13. package/bin/lib/i18n.js +0 -3
  14. package/bin/lib/manifest.js +1 -1
  15. package/bin/phases/claude-runtime.js +1 -3
  16. package/bin/phases/codex-runtime.js +4 -2
  17. package/bin/phases/copy-payload.js +3 -15
  18. package/bin/phases/post-install.js +5 -17
  19. package/bin/phases/root-config.js +2 -3
  20. package/bin/phases/select-platform.js +2 -5
  21. package/bin/phases/summary.js +0 -1
  22. package/package.json +2 -5
  23. package/src/claude/CLAUDE.md +19 -6
  24. package/src/claude/agents/project-manager.md +41 -9
  25. package/src/claude/agents/spec-maker.md +76 -155
  26. package/src/claude/gitignore +1 -1
  27. package/src/claude/hooks/__tests__/spec-gate.test.js +162 -0
  28. package/src/claude/hooks/lib/context.cjs +1 -1
  29. package/src/claude/hooks/lib/detect.cjs +1 -1
  30. package/src/claude/hooks/spec-gate.cjs +44 -9
  31. package/src/claude/hooks/spec-state.cjs +22 -11
  32. package/src/claude/hooks/task-scaffold-guard.cjs +1 -1
  33. package/src/claude/migration-manifest.json +17 -3
  34. package/src/claude/rules/manage-docs.md +43 -24
  35. package/src/claude/rules/state-sync.md +57 -62
  36. package/src/claude/rules/workflow.md +30 -13
  37. package/src/claude/scripts/change-firewall.cjs +2 -2
  38. package/src/claude/scripts/generate-skill-catalog.cjs +0 -1
  39. package/src/claude/scripts/spec-receipt.cjs +141 -1
  40. package/src/claude/scripts/spec-resolver.cjs +288 -0
  41. package/src/claude/skills/develop/SKILL.md +89 -125
  42. package/src/claude/skills/develop/references/parallel-waves.md +75 -162
  43. package/src/claude/skills/develop/references/quality-gate.md +61 -121
  44. package/src/claude/skills/develop/references/subagent-patterns.md +31 -38
  45. package/src/claude/skills/question/SKILL.md +1 -1
  46. package/src/claude/skills/specs/SKILL.md +142 -204
  47. package/src/claude/skills/specs/references/review.md +109 -243
  48. package/src/claude/skills/specs/references/templates.md +173 -0
  49. package/src/claude/skills/sync/SKILL.md +66 -90
  50. package/src/claude/skills/sync/references/sync-protocols.md +62 -83
  51. package/src/codex/AGENTS.md +20 -7
  52. package/src/codex/hooks/lib/spec-receipt.cjs +18 -0
  53. package/src/codex/hooks/lib/spec-utils.cjs +25 -0
  54. package/src/codex/hooks/spec-gate.cjs +46 -16
  55. package/src/codex/hooks/spec-state.cjs +22 -12
  56. package/src/codex/rules/state-sync.md +57 -61
  57. package/src/common/AGENTS.md +8 -1
  58. package/bin/__tests__/opencode-spec-gate.test.js +0 -799
  59. package/bin/lib/opencode-install.js +0 -899
  60. package/bin/phases/opencode-runtime.js +0 -38
  61. package/src/claude/skills/specs/references/archive-workflow.md +0 -74
  62. package/src/claude/skills/specs/references/ask-user-question-gates.md +0 -70
  63. package/src/claude/skills/specs/references/codebase-analysis.md +0 -102
  64. package/src/claude/skills/specs/references/cross-spec-dependency.md +0 -76
  65. package/src/claude/skills/specs/references/research-strategy.md +0 -107
  66. package/src/claude/skills/specs/references/scope-inquiry.md +0 -107
  67. package/src/claude/skills/specs/references/translation-mirror.md +0 -83
  68. package/src/claude/skills/specs/rules/design-discovery-full.md +0 -45
  69. package/src/claude/skills/specs/rules/design-discovery-light.md +0 -28
  70. package/src/claude/skills/specs/rules/design-principles.md +0 -138
  71. package/src/claude/skills/specs/rules/design-review.md +0 -55
  72. package/src/claude/skills/specs/rules/ears-format.md +0 -42
  73. package/src/claude/skills/specs/rules/phase-decision-matrix.md +0 -56
  74. package/src/claude/skills/specs/rules/task-scoring-rubric.md +0 -52
  75. package/src/claude/skills/specs/rules/tasks-generation.md +0 -189
  76. package/src/opencode/AGENTS.md +0 -41
  77. package/src/opencode/plugins/docs-sync.ts +0 -175
  78. package/src/opencode/plugins/inspect-block.ts +0 -164
  79. package/src/opencode/plugins/privacy-block.ts +0 -182
  80. package/src/opencode/plugins/rules.ts +0 -155
  81. package/src/opencode/plugins/session.ts +0 -179
  82. package/src/opencode/plugins/spec-gate.ts +0 -457
  83. package/src/opencode/plugins/spec-state.ts +0 -311
  84. package/src/opencode/plugins/state.ts +0 -253
  85. package/src/opencode/plugins/task-scaffold-guard.ts +0 -161
  86. package/src/opencode/plugins/usage.ts +0 -178
@@ -68,6 +68,105 @@ function candidateError(reason, code = 'malformed') {
68
68
  return error;
69
69
  }
70
70
 
71
+ const WORKFLOW_STATUSES = new Set(['pending', 'in_progress', 'paused', 'blocked', 'done']);
72
+ const WORKFLOW_TASK_NAME = /^task-[A-Za-z0-9][A-Za-z0-9._-]*\.md$/;
73
+
74
+ function workflowTaskStatus(content, taskName) {
75
+ const matches = String(content || '').matchAll(/^\s*(?:\*\*)?Status(?:\*\*)?\s*:\s*(?:\*\*)?\s*([A-Za-z_-]+)(?:\*\*)?\s*$/gim);
76
+ const values = [...matches].map((match) => match[1].toLowerCase().replaceAll('-', '_'));
77
+ if (values.length !== 1 || !WORKFLOW_STATUSES.has(values[0])) {
78
+ throw candidateError(`${taskName} must contain exactly one supported Status field`);
79
+ }
80
+ return values[0];
81
+ }
82
+
83
+ function inspectWorkflowFeature(specsDir, canonicalSpecs, requestedName) {
84
+ const featureDir = path.join(specsDir, requestedName);
85
+ let featureLstat;
86
+ try {
87
+ featureLstat = fs.lstatSync(featureDir);
88
+ } catch (error) {
89
+ if (isMissingError(error)) return { missing: true };
90
+ throw candidateError(`feature lstat error: ${error.message}`);
91
+ }
92
+ if (featureLstat.isSymbolicLink() || !featureLstat.isDirectory()) {
93
+ throw candidateError('workflow feature entry must be a regular directory');
94
+ }
95
+
96
+ const canonicalFeature = fs.realpathSync(featureDir);
97
+ if (!isPathInside(canonicalSpecs, canonicalFeature) || path.dirname(canonicalFeature) !== canonicalSpecs) {
98
+ throw candidateError('workflow feature directory must be a direct child of specs root');
99
+ }
100
+
101
+ const featureName = path.basename(canonicalFeature);
102
+ if (fs.existsSync(path.join(canonicalFeature, 'spec.json'))) {
103
+ return { legacyPresent: true, featureName, featureDir: canonicalFeature };
104
+ }
105
+
106
+ const planFile = path.join(canonicalFeature, 'plan.md');
107
+ let planLstat;
108
+ try {
109
+ planLstat = fs.lstatSync(planFile);
110
+ } catch (error) {
111
+ if (isMissingError(error)) return { missingWorkflow: true, featureName, featureDir: canonicalFeature };
112
+ throw candidateError(`plan.md lstat error: ${error.message}`);
113
+ }
114
+ if (planLstat.isSymbolicLink() || !planLstat.isFile() || fs.realpathSync(planFile) !== planFile) {
115
+ throw candidateError('plan.md must be a regular non-symlink file in the workflow feature directory');
116
+ }
117
+
118
+ const taskNames = fs.readdirSync(canonicalFeature, { withFileTypes: true })
119
+ .filter((entry) => WORKFLOW_TASK_NAME.test(entry.name))
120
+ .map((entry) => entry.name)
121
+ .sort();
122
+ if (taskNames.length === 0) {
123
+ return { missingWorkflow: true, featureName, featureDir: canonicalFeature };
124
+ }
125
+
126
+ const tasks = taskNames.map((taskName) => {
127
+ const taskFile = path.join(canonicalFeature, taskName);
128
+ const taskLstat = fs.lstatSync(taskFile);
129
+ if (taskLstat.isSymbolicLink() || !taskLstat.isFile() || fs.realpathSync(taskFile) !== taskFile) {
130
+ throw candidateError(`${taskName} must be a regular non-symlink file in the workflow feature directory`);
131
+ }
132
+ return {
133
+ path: taskName,
134
+ status: workflowTaskStatus(fs.readFileSync(taskFile, 'utf8'), taskName),
135
+ };
136
+ });
137
+ const taskRegistry = Object.fromEntries(tasks.map((task) => [task.path, { status: task.status }]));
138
+ const allTasksDone = tasks.every((task) => task.status === 'done');
139
+ return {
140
+ layoutKind: 'process-v3',
141
+ featureName,
142
+ specsDir: canonicalSpecs,
143
+ featureDir: canonicalFeature,
144
+ planFile,
145
+ stateFile: planFile,
146
+ phase: allTasksDone ? 'done' : 'execution',
147
+ taskRegistry,
148
+ tasks,
149
+ allTasksDone,
150
+ };
151
+ }
152
+
153
+ function normalizeLegacyWorkflowCandidate(candidate) {
154
+ const registry = candidate.spec?.task_registry || {};
155
+ const tasks = Object.entries(registry).map(([taskPath, task]) => ({
156
+ path: taskPath,
157
+ status: task?.status || 'pending',
158
+ }));
159
+ return {
160
+ ...candidate,
161
+ layoutKind: 'legacy-spec',
162
+ stateFile: candidate.specFile,
163
+ phase: candidate.spec?.current_phase || candidate.spec?.phase || 'unknown',
164
+ taskRegistry: registry,
165
+ tasks,
166
+ allTasksDone: tasks.length > 0 && tasks.every((task) => task.status === 'done'),
167
+ };
168
+ }
169
+
71
170
  function inspectFeature(specsDir, canonicalSpecs, requestedName) {
72
171
  const featureDir = path.join(specsDir, requestedName);
73
172
  let featureLstat;
@@ -491,6 +590,193 @@ function resolvePersistedSpec({ projectRoot, runtime, explicitFeature, explicitP
491
590
  }
492
591
  }
493
592
 
593
+ function scanWorkflowFeatures(specsDir) {
594
+ let canonicalSpecs;
595
+ try {
596
+ canonicalSpecs = fs.realpathSync(specsDir);
597
+ } catch (error) {
598
+ if (isMissingError(error)) return { active: [], candidates: [], invalid: [] };
599
+ return { active: [], candidates: [], invalid: [{ featureName: '<specs>', reason: error.message }] };
600
+ }
601
+
602
+ const active = [];
603
+ const candidates = [];
604
+ const invalid = [];
605
+ const entries = fs.readdirSync(canonicalSpecs, { withFileTypes: true })
606
+ .filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
607
+ .sort((left, right) => left.name.localeCompare(right.name));
608
+ for (const entry of entries) {
609
+ try {
610
+ const candidate = inspectWorkflowFeature(canonicalSpecs, canonicalSpecs, entry.name);
611
+ if (candidate.missing || candidate.missingWorkflow || candidate.legacyPresent) continue;
612
+ candidates.push(candidate);
613
+ if (!candidate.allTasksDone) active.push(candidate);
614
+ } catch (error) {
615
+ invalid.push({ featureName: entry.name, reason: error.message, planFile: path.join(canonicalSpecs, entry.name, 'plan.md') });
616
+ }
617
+ }
618
+ return { active, candidates, invalid };
619
+ }
620
+
621
+ function explicitWorkflowFeatureName({ projectRoot, specsDir, explicitFeature, explicitPath }) {
622
+ if (explicitFeature !== undefined && explicitFeature !== null) {
623
+ if (typeof explicitFeature !== 'string' || explicitFeature.trim() === ''
624
+ || explicitFeature.includes('/') || explicitFeature.includes('\\') || explicitFeature.includes('..')) {
625
+ return { error: 'explicit_malformed', explicitFeature, reason: `malformed feature name: ${explicitFeature}` };
626
+ }
627
+ return { featureName: explicitFeature.trim() };
628
+ }
629
+
630
+ if (typeof explicitPath !== 'string' || explicitPath.trim() === '') {
631
+ return { error: 'explicit_malformed', explicitPath, reason: 'empty explicit path' };
632
+ }
633
+ const targetPath = path.resolve(projectRoot, explicitPath);
634
+ let targetLstat;
635
+ try {
636
+ targetLstat = fs.lstatSync(targetPath);
637
+ } catch (error) {
638
+ return isMissingError(error)
639
+ ? { error: 'explicit_not_found', explicitPath, reason: `workflow target not found at ${explicitPath}` }
640
+ : { error: 'explicit_malformed', explicitPath, reason: `explicit path stat error: ${error.message}` };
641
+ }
642
+ if (targetLstat.isSymbolicLink()) {
643
+ return { error: 'explicit_malformed', explicitPath, reason: 'explicit workflow target must not be a symlink' };
644
+ }
645
+
646
+ let featureDir;
647
+ if (targetLstat.isDirectory()) {
648
+ featureDir = targetPath;
649
+ } else if (targetLstat.isFile()
650
+ && (path.basename(targetPath) === 'plan.md' || WORKFLOW_TASK_NAME.test(path.basename(targetPath)))) {
651
+ featureDir = path.dirname(targetPath);
652
+ } else {
653
+ return { error: 'explicit_malformed', explicitPath, reason: 'explicit workflow path must target a feature directory, plan.md, or flat task-*.md' };
654
+ }
655
+ const relative = path.relative(path.resolve(specsDir), featureDir);
656
+ if (!relative || relative.includes(path.sep) || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
657
+ return { error: 'explicit_malformed', explicitPath, reason: 'explicit workflow path must target a direct child of specs root' };
658
+ }
659
+ return { featureName: relative };
660
+ }
661
+
662
+ /**
663
+ * Resolve the authoring/execution workflow visible to prompt and Stop hooks.
664
+ * Persisted spec resolution remains unchanged; process-v3 is an additive adapter
665
+ * and never becomes completion-authority input by way of findAllSpecCandidates.
666
+ */
667
+ function resolveWorkflowCandidate({ projectRoot, runtime, explicitFeature, explicitPath, target, includeCompleted = false } = {}) {
668
+ if (!projectRoot) throw new TypeError('projectRoot required');
669
+ const normalized = extractExplicitTarget(
670
+ target,
671
+ explicitFeature !== undefined ? { explicitFeature } : null,
672
+ explicitPath !== undefined ? { explicitPath } : null,
673
+ );
674
+ if (normalized) {
675
+ if (Object.prototype.hasOwnProperty.call(normalized, 'explicitFeature')) explicitFeature = normalized.explicitFeature;
676
+ if (Object.prototype.hasOwnProperty.call(normalized, 'explicitPath')) explicitPath = normalized.explicitPath;
677
+ }
678
+ const hasExplicit = explicitFeature !== undefined && explicitFeature !== null
679
+ || explicitPath !== undefined && explicitPath !== null;
680
+ let specsDir;
681
+ try {
682
+ specsDir = specsDirectory(projectRoot, runtime || {});
683
+ } catch (error) {
684
+ return { error: hasExplicit ? 'explicit_malformed' : 'invalid_specs', candidates: ['<specs>'], reason: error.message };
685
+ }
686
+
687
+ if (hasExplicit) {
688
+ const legacyDirect = resolveActiveSpec({ projectRoot, runtime, explicitFeature, explicitPath });
689
+ if (legacyDirect && !legacyDirect.error) return normalizeLegacyWorkflowCandidate(legacyDirect);
690
+ const identity = explicitWorkflowFeatureName({ projectRoot, specsDir, explicitFeature, explicitPath });
691
+ if (identity.error) return identity;
692
+
693
+ const legacy = resolveActiveSpec({ projectRoot, runtime, explicitFeature: identity.featureName });
694
+ if (legacy && !legacy.error) return normalizeLegacyWorkflowCandidate(legacy);
695
+ if (legacy && legacy.error !== 'explicit_not_found') return legacy;
696
+
697
+ let canonicalSpecs;
698
+ try {
699
+ canonicalSpecs = fs.realpathSync(specsDir);
700
+ const candidate = inspectWorkflowFeature(specsDir, canonicalSpecs, identity.featureName);
701
+ if (candidate.missing || candidate.missingWorkflow || candidate.legacyPresent) {
702
+ return { error: 'explicit_not_found', explicitFeature: identity.featureName, explicitPath, reason: `workflow not found for feature ${identity.featureName}` };
703
+ }
704
+ return candidate;
705
+ } catch (error) {
706
+ return { error: 'explicit_malformed', explicitFeature: identity.featureName, explicitPath, reason: error.message };
707
+ }
708
+ }
709
+
710
+ let legacyActive;
711
+ try {
712
+ const legacyCandidates = includeCompleted
713
+ ? findAllSpecCandidates(projectRoot, runtime || {})
714
+ : findAllActiveSpecs(projectRoot, runtime || {});
715
+ legacyActive = legacyCandidates.map(normalizeLegacyWorkflowCandidate);
716
+ } catch (error) {
717
+ return {
718
+ error: 'invalid_specs',
719
+ candidates: Array.isArray(error.invalid) ? error.invalid.map((entry) => entry.featureName) : ['<specs>'],
720
+ invalid: error.invalid || [],
721
+ reason: error.message,
722
+ };
723
+ }
724
+ const workflow = scanWorkflowFeatures(specsDir);
725
+ if (workflow.invalid.length > 0) {
726
+ return {
727
+ error: 'invalid_specs',
728
+ candidates: workflow.invalid.map((entry) => entry.featureName),
729
+ invalid: workflow.invalid,
730
+ reason: `Invalid workflow candidates: ${workflow.invalid.map((entry) => `${entry.featureName}: ${entry.reason}`).join('; ')}`,
731
+ };
732
+ }
733
+ const workflowCandidates = includeCompleted ? workflow.candidates : workflow.active;
734
+ const active = [...legacyActive, ...workflowCandidates]
735
+ .sort((left, right) => left.featureName.localeCompare(right.featureName));
736
+ if (active.length === 0) return null;
737
+ if (active.length === 1) return active[0];
738
+ return {
739
+ error: includeCompleted ? 'multiple_persisted' : 'multiple_active',
740
+ candidates: active.map((candidate) => candidate.featureName),
741
+ active,
742
+ reason: `Multiple ${includeCompleted ? 'persisted' : 'active'} workflows found: ${active.map((candidate) => candidate.featureName).join(', ')}. Provide explicit feature.`,
743
+ };
744
+ }
745
+
746
+ /**
747
+ * Narrow a Stop-gate ambiguity without changing legacy persisted-spec rules.
748
+ * A single unfinished process-v3 packet is the current workflow; completed
749
+ * packets are historical. If every process-v3 packet is complete, return the
750
+ * set so the gate can revalidate all receipts instead of blocking on identity.
751
+ */
752
+ function refineWorkflowGateResolution(resolved) {
753
+ if (!resolved || resolved.error !== 'multiple_persisted' || !Array.isArray(resolved.active)) {
754
+ return resolved;
755
+ }
756
+ const candidates = resolved.active;
757
+ if (candidates.length === 0 || candidates.some((candidate) => candidate.layoutKind !== 'process-v3')) {
758
+ return resolved;
759
+ }
760
+
761
+ const unfinished = candidates.filter((candidate) => !candidate.allTasksDone);
762
+ if (unfinished.length === 1) return unfinished[0];
763
+ if (unfinished.length > 1) {
764
+ return {
765
+ ...resolved,
766
+ error: 'multiple_active',
767
+ candidates: unfinished.map((candidate) => candidate.featureName),
768
+ active: unfinished,
769
+ reason: `Multiple active workflows found: ${unfinished.map((candidate) => candidate.featureName).join(', ')}. Provide explicit feature.`,
770
+ };
771
+ }
772
+ return {
773
+ layoutKind: 'process-v3-completed-set',
774
+ candidates,
775
+ specsDir: candidates[0].specsDir,
776
+ allTasksDone: true,
777
+ };
778
+ }
779
+
494
780
  module.exports = {
495
781
  specsDirectory,
496
782
  findAllActiveSpecs,
@@ -498,4 +784,6 @@ module.exports = {
498
784
  extractExplicitTarget,
499
785
  resolveActiveSpec,
500
786
  resolvePersistedSpec,
787
+ resolveWorkflowCandidate,
788
+ refineWorkflowGateResolution,
501
789
  };
@@ -1,187 +1,151 @@
1
1
  ---
2
2
  name: hapo:develop
3
- description: "Implement an explicitly invoked ready or low-risk change with policy-proportional evidence and one closeout owner."
3
+ description: "Implement an explicitly requested process-first feature or task, run its real verification, and close it with one evidence owner. Also supports existing legacy Specs packets."
4
4
  user-invocable: true
5
- when_to_use: "Use to implement a ready spec, a specific task, or a Direct low-risk change."
5
+ when_to_use: "Use to implement a ready feature packet, one named task, or a clear low-risk change."
6
6
  category: utilities
7
7
  keywords: [implementation, specs, verification]
8
8
  argument-hint: "[feature-name|specs-directory-path] [task-file] [--flash] [--parallel [N]] [--notes]"
9
9
  metadata:
10
10
  author: haposoft
11
- version: "1.0.0"
11
+ version: "3.0.0"
12
12
  ---
13
- # Develop — implementation and closeout contract
13
+ # Develop — implement, prove, synchronize
14
14
 
15
- Implement the requested scope, then close it with one canonical execution owner.
16
- For a durable spec, `spec.json` is machine authority and Markdown is a human
17
- projection. The v2.1 policy snapshot persists minimum input; lane, label,
18
- artifact profile, and ceremony are derived views. The executable policy source
19
- is `src/claude/scripts/workflow-policy.cjs` (installed as
20
- `.claude/scripts/workflow-policy.cjs`).
15
+ Implement only the explicitly requested scope. For a process-first feature,
16
+ `plan.md` and flat tasks are the durable packet; one closeout owner coordinates verification, review, sync, and docs impact without borrowing evidence.
17
+
18
+ Develop never starts merely because Specs finished. The user invokes it.
21
19
 
22
20
  ## Usage and pre-state guard
23
21
 
24
22
  ```text
25
23
  /hapo:develop <feature>
26
24
  /hapo:develop specs/<feature>
27
- /hapo:develop <feature> <specific-task-file.md>
25
+ /hapo:develop <feature> task-02-<slug>.md
28
26
  /hapo:develop <feature> --flash
29
27
  /hapo:develop <feature> --parallel [N]
30
28
  /hapo:develop <feature> --notes
31
29
  ```
32
30
 
33
- `--notes` is opt-in. Without it, do not create or update `implementation-notes.html`; there is no notes opt-out flag. When requested, load `references/implementation-notes-template.html` and record concrete decisions, gaps, scope-escape exceptions, risks, and verification caveats.
31
+ `--notes` is opt-in. Only then load `references/implementation-notes-template.html`;
32
+ record decisions, scope exceptions, risks, gaps, and verification limits. Never create it by default.
34
33
 
35
- Run the policy pre-state guard before loading a spec or mutating state. The incompatible pair must exit `2` and create no state, receipt, worktree, subagent, or commit: this is the `flash+parallel fail-fast` gate.
34
+ Before any state mutation, reject `--flash` combined with `--parallel`:
36
35
 
37
36
  ```bash
38
37
  node .claude/scripts/workflow-policy.cjs --flash --parallel --json
39
38
  ```
40
39
 
41
- Expected message:
42
-
43
- ```text
44
- Unsupported flags: --flash and --parallel are incompatible.
45
- Remediation: run `/hapo:develop <feature> --flash` or `/hapo:develop <feature> --parallel [N]`.
46
- No spec state, task receipt, worktree, subagent, or commit was created.
47
- ```
48
-
49
- ## Policy authority
50
-
51
- For durable specs, persisted `workflow_policy` authority is the v2.1 minimum.
52
- Canonical authoring supplies `planning_depth`, `assurance_level`,
53
- `classified_minimum`, and normalized `risks`; lane and ceremony remain derived
54
- views.
55
-
56
- Classify before spec/state mutation:
57
-
58
- ```bash
59
- node .claude/scripts/workflow-policy.cjs --classify-lane --task-json '<task JSON>' --json
60
- ```
61
-
62
- - **Direct**: clear, isolated, reversible, low-risk. Skip spec/state/registry ceremony and run targeted verification with proportional evidence.
63
- - **Standard**: use the bounded spec profile and focused inspection. A small feature needs no task registry/bundle and closes once at the feature boundary.
64
- - **Critical**: derived Strict view. Add durable task state, research, or review
65
- only when actual topology, uncertainty, or assurance requires it.
40
+ The pair exits `2` before a task edit, receipt, worktree, subagent, or commit. Do not reproduce it with a looser parser.
66
41
 
67
- Risk can raise the classified minimum. Reclassify before persistence. After
68
- persistence, a per-feature baseline is monotonic and no downgrade is supported
69
- until a trusted issuer exists; the baseline applies only to that feature.
70
- Legacy approval fields and caller booleans have zero authority.
42
+ ## Resolve the work packet
71
43
 
72
- Develop starts only from the user's explicit Develop invocation. Technical
73
- `ready_for_implementation` never dispatches Develop, and Specs must never
74
- auto-chain into this workflow.
44
+ 1. Resolve one `specs/<feature>/plan.md` plus flat `task-NN-*.md`; an explicit task path wins.
45
+ 2. Read C1 scope, exclusions, acceptance mapping, ownership, and task order.
46
+ 3. A task is unblocked only when every named dependency is done with a valid inline Receipt.
47
+ 4. Full-feature mode selects one unblocked task at a time, in plan order.
48
+ Specific-task mode implements only that task and then stops.
49
+ 5. Without a packet, work directly only when the change is clear, isolated, reversible, and low-risk.
75
50
 
76
- `execution_tier` (`Light | Standard | Deep`) is a read-only legacy adapter (`Direct | Standard | Critical`). It cannot emit policy, create obligations, select a workflow, or override the lane. Do not branch on it for new behavior.
51
+ Ambiguity, overlapping ownership, a dependency cycle, or a missing Verification Plan is a blocker. Do not guess.
77
52
 
78
53
  ## Modes
79
54
 
80
- ### Specific task
55
+ ### Sequential feature or specific task
81
56
 
82
- Load exactly one task packet, implement only its scope, run its evidence, and stop after synchronization. Specific-task mode never selects or chains into another pending task.
57
+ Set the selected task's single `Status:` field to `in_progress`. Implement its
58
+ Outcome and Acceptance only. Do not start the next task until proof, review,
59
+ and sync for the current task finish. A specific-task invocation never chains.
83
60
 
84
- ### Full feature
61
+ ### Parallel (`--parallel [N]`)
85
62
 
86
- For a task-bearing policy, resolve one unblocked task at a time, complete its cycle, synchronize, and recompute the queue. A bounded Standard feature without tasks runs one feature-level cycle. Stop on the first unresolved block or missing proof; run final integration only after all pending tasks finish.
63
+ Load `references/parallel-waves.md`. Require disjoint writes, satisfied dependencies,
64
+ isolated worktrees, a bounded wave, and one controller writer; otherwise work sequentially.
87
65
 
88
- ### Parallel (opt-in)
66
+ ### Flash (`--flash`)
89
67
 
90
- Load `references/parallel-waves.md` only with `--parallel`. It may use isolated worktrees and a bounded wave, but lane obligations, single-writer rules, immutable provenance, and final integration remain in force. Otherwise process one unblocked task at a time.
68
+ Flash is an explicit speed trade-off, never completion:
91
69
 
92
- ### 3. Flash Mode (`--flash`)
93
- Flash is an explicit speed trade-off, not a completion shortcut:
94
-
95
- - skip dedicated test suites, full task evidence execution, and extended manual
96
- checks;
97
- - run only a cheap available syntax/typecheck/compile preflight;
98
- - never weaken or delete tests;
99
- - synchronize only as `in_progress` with `FLASH_UNVERIFIED`,
100
- `dependencyBlocked: true`, `unblocks: false`, and blocker
70
+ - run only an available cheap syntax, typecheck, or compile preflight;
71
+ - skip dedicated tests, extended manual checks, and review retry loops;
72
+ - keep `Status: in_progress` and record `FLASH_UNVERIFIED` plus blocker
101
73
  `awaiting /hapo:test <feature>`;
102
- - never claim `Test PASS`, `Evidence PASS`, `Auto-Approved`, or
103
- `production-ready`.
74
+ - do not unblock dependents or report Test PASS, Evidence PASS,
75
+ production-ready, or done;
76
+ - promotion later requires a fresh canonical PASS receipt through the trusted
77
+ sync-finalize path.
104
78
 
105
- Only trusted sync-finalize may promote a current `FLASH_UNVERIFIED` task with explicit `--verdict PASS` and canonical proof; caller promotion fields are ignored/rejected, and marker-only proof never promotes. Stored state remains `in_progress`/`FLASH_UNVERIFIED`, dependency-blocked with a concrete blocker, omits promotion fields, and binds proof to expected Base/Head anchors.
106
- ## Workflow
107
- `Classify lane → load lane artifacts → scout obligations → implement current
108
- scope → one closeout owner → test receipt → review → docs-impact sync.`
79
+ Never weaken, delete, or rewrite tests to make Flash look complete.
109
80
 
110
- ### Step 1 — Load and scope
81
+ ## Task cycle
111
82
 
112
- - Direct does not create a spec, state, registry, task bundle, or notes file.
113
- - Standard requires its bounded validated artifacts, but not a
114
- registry when the scope is small and no task obligation is persisted.
115
- - Critical requires only the artifacts named by its snapshot obligations.
116
- - For task-bearing work, load one requested task or one unblocked task and
117
- extract the exact seven v2.1 sections: `Outcome`, `Scope`, `Anchors and Ownership`,
118
- `Changes`, `Acceptance`, `Dependencies`, and `Verification Plan`.
119
- Use typed `coordination.boundaries` for ownership/DAG/proof/parallel authority.
120
- - Before implementation, transition `pending` to `in_progress` in task Markdown `**Status:**` and `spec.json.task_registry[path]` with synchronized timestamps. Never write execution proof into the task plan.
121
- - A spec-ready execution-evidence or independent-audit slot may be `PENDING`
122
- before implementation; pending is not proof and cannot close the feature.
83
+ ### 1. Scout
123
84
 
124
- ### Step 2 — Scout
85
+ Trace entrypoints, callers, dependents, registration, errors, and owned files.
86
+ Compilation does not prove an unmounted UI, unregistered route, uncalled service, or missing consumer.
125
87
 
126
- Scout depth follows assurance, risk, and blast radius. Identify real
127
- entrypoints/callers, integration points, dependents, reachability risks, and
128
- safe scope. File count is not review depth; stop if a runtime entrypoint cannot
129
- be grounded.
88
+ ### 2. Implement
130
89
 
131
- ### Step 3 — Implement
90
+ Honor Scope, Ownership, Acceptance, and Dependencies. Do not silently replace named contracts;
91
+ scope expansion requires evidence and a return to C1, not implementation convenience.
132
92
 
133
- Implement only the scoped behavior and named contracts. Do not silently replace
134
- frameworks, auth, transport, storage, or runtime boundaries. Do not mark a
135
- runtime-facing file complete while it is orphaned, unmounted, unregistered, or
136
- uncalled. A specific-task request ends after that task; no automatic chain.
93
+ ### 3. Verify and review once
137
94
 
138
- <SCOPE-FIDELITY>
139
- The `scope_lock`, requirements, design contracts, and active task are
140
- the implementation contract. Scope escape requires a concrete reachability or
141
- compile reason and must not deliver a later task early.
142
- </SCOPE-FIDELITY>
95
+ The test owner executes the exact Verification Plan and records real output.
96
+ The review owner, when risk or repository policy requires review, evaluates
97
+ correctness, security, scope, and reachability but does not manufacture proof.
98
+ Use `PASS | PASS_WITH_WARNINGS | FAIL | BLOCKED`; only literal PASS can close.
143
99
 
144
- ### Step 4 — Closeout owner
100
+ Load `references/quality-gate.md` for the closeout contract. Remediate an
101
+ observed failure, then rerun only affected proof and review. Do not blind-retry
102
+ a blocked environment. After three failed repair rounds, stop and ask the user.
145
103
 
146
- The controller assigns exactly one closeout owner for the current task or feature. It invokes testing once, then review once when required; no phase re-runs either as a hidden gate.
104
+ ### 4. Write the inline Receipt and sync
147
105
 
148
- - For task-bearing work, the test owner executes the Verification Plan and writes the canonical execution receipt to `receipts/<task-basename>.md` only after real execution.
149
- - The review owner evaluates correctness, security, and spec compliance; it
150
- consumes existing proof but never creates or claims execution proof.
151
- - Review depth follows `assurance_level`, risk, and blast radius. Lane is only
152
- a derived view, and depth does not follow the
153
- number of files changed.
154
- - Finding count never selects review depth or overrides missing execution proof.
155
- - Use the shared adapter surface: `PASS | PASS_WITH_WARNINGS | FAIL | BLOCKED`.
156
- Warnings do not waive missing proof; `FAIL` is remediable and `BLOCKED` stops.
106
+ After a real pass, append or replace the task's final `## Receipt` with:
157
107
 
158
- Load `references/quality-gate.md` for the exact closeout contract. It has no
159
- fixed Light/Standard/Deep agent sequence.
108
+ - `Verification: PASS`;
109
+ - the exact `Command:` and `Exit: 0`;
110
+ - runtime-derived `Base:` and `Head:`;
111
+ - a non-empty fenced block containing current command output;
112
+ - required negative, reachability, and artifact proof from the task.
160
113
 
161
- ### Step 5 — Sync and docs checkpoint
114
+ Then set `Status: done`, update only implemented checkboxes, and recompute the
115
+ queue. Missing, stale, contradictory, zero-test, placeholder, or marker-only
116
+ proof leaves the task unfinished.
162
117
 
163
- Only a canonical receipt with task identity/path, exact command/result, expected versus observed behavior, bound provenance, applicable negative/reachability proof, and required artifact hashes can close a task. Prefer `receipts/<task-basename>.md`; legacy task `## Evidence` is read-compatible, but conflicting proof identities fail closed. Missing, pending, marker-only, or contradictory evidence stays unfinished. Critical audits need distinct session IDs, expected Base/Head binding, concrete evidence, and literal `verdict: "PASS"`; `Audit: PASS` is not evidence.
164
- After verification, synchronize status/timestamps/task state. Then evaluate
165
- actual docs impact:
118
+ ### 5. Finish the requested boundary
166
119
 
167
- - `none`: record it and stop;
168
- - `minor`/`major`: update only affected existing docs through the normal docs
169
- workflow, with no broad refresh.
120
+ Specific-task mode stops after its sync. Full-feature mode continues one task
121
+ at a time, then runs the feature-level integration command and reachability
122
+ check named by the repository or plan. Show current evidence and limitations at
123
+ C3; the user decides completion.
170
124
 
171
- Do not run a docs checkpoint merely because a task finished.
125
+ Evaluate docs impact from the behavior actually changed:
172
126
 
173
- ## Definition of done and handoff
127
+ - `none`: report it and make no docs edit;
128
+ - `minor` or `major`: update only affected existing docs through the docs flow.
174
129
 
175
- Done requires active criteria, all required evidence, reachable runtime behavior,
176
- a receipt bound to expected Base/Head, and every lane obligation. Only literal
177
- `PASS` can complete; `PASS_WITH_WARNINGS` remains unfinished.
178
- `--flash` records only `FLASH_UNVERIFIED` and never done.
130
+ ## Definition of done
179
131
 
180
- For a full feature, after the last task run the **Final Integration Scout** for runtime-facing surfaces and compare reachability/scope before completion. The same test owner creates `feature-receipt.md` exactly once from final integration proof. Task-bearing closeout requires every task receipt plus that feature receipt; taskless Compact/Full creates only the feature receipt at closeout. Its earlier absence is normal. A specific task stops after synchronization and creates no feature receipt unless it also completes the full integration boundary.
132
+ Done requires satisfied acceptance, reachable behavior, current executable
133
+ proof, correct Base/Head binding, resolved blocking review findings, and no
134
+ scope substitution. `PASS_WITH_WARNINGS`, `NO_TESTS`, `FLASH_UNVERIFIED`, or a
135
+ review-only pass cannot close work.
181
136
 
182
137
  ## Attached references
183
138
 
184
- - `references/quality-gate.md` — closeout ownership, verdict normalization, and
185
- evidence checks.
186
- - `references/parallel-waves.md` — opt-in worktree waves.
187
- - `references/implementation-notes-template.html` — loaded only with `--notes`.
139
+ - `references/quality-gate.md` — proof and review ownership.
140
+ - `references/parallel-waves.md` — opt-in isolated worktree waves.
141
+ - `references/implementation-notes-template.html` — only with `--notes`.
142
+
143
+ ## Legacy workflow compatibility
144
+
145
+ If the selected feature contains `spec.json`, use its persisted
146
+ `workflow_policy`, `task_registry`, nested task paths, typed boundaries,
147
+ separate task receipts, and feature closeout receipt exactly as the legacy
148
+ adapters require. Preserve `planning_depth`, `assurance_level`, derived lane,
149
+ and read-only `execution_tier`; do not project these fields into a new v3
150
+ packet. Keep JSON and Markdown status synchronized, and use the existing
151
+ completion-authority path for final closeout.