@wazir-dev/cli 1.3.0 → 1.4.0

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 (133) hide show
  1. package/CHANGELOG.md +17 -2
  2. package/docs/research/2026-03-20-agents/a18fb002157904af5.txt +187 -0
  3. package/docs/research/2026-03-20-agents/a1d0ac79ac2f11e6f.txt +2 -0
  4. package/docs/research/2026-03-20-agents/a324079de037abd7c.txt +198 -0
  5. package/docs/research/2026-03-20-agents/a357586bccfafb0e5.txt +256 -0
  6. package/docs/research/2026-03-20-agents/a4365394e4d753105.txt +137 -0
  7. package/docs/research/2026-03-20-agents/a492af28bc52d3613.txt +136 -0
  8. package/docs/research/2026-03-20-agents/a4984db0b6a8eee07.txt +124 -0
  9. package/docs/research/2026-03-20-agents/a5b30e59d34bbb062.txt +214 -0
  10. package/docs/research/2026-03-20-agents/a5cf7829dab911586.txt +165 -0
  11. package/docs/research/2026-03-20-agents/a607157c30dd97c9e.txt +96 -0
  12. package/docs/research/2026-03-20-agents/a60b68b1e19d1e16b.txt +115 -0
  13. package/docs/research/2026-03-20-agents/a722af01c5594aba0.txt +166 -0
  14. package/docs/research/2026-03-20-agents/a787bdc516faa5829.txt +181 -0
  15. package/docs/research/2026-03-20-agents/a7c46d1bba1056ed2.txt +132 -0
  16. package/docs/research/2026-03-20-agents/a7e5abbab2b281a0d.txt +100 -0
  17. package/docs/research/2026-03-20-agents/a8dbadc66cd0d7d5a.txt +95 -0
  18. package/docs/research/2026-03-20-agents/a904d9f45d6b86a6d.txt +75 -0
  19. package/docs/research/2026-03-20-agents/a927659a942ee7f60.txt +102 -0
  20. package/docs/research/2026-03-20-agents/a962cb569191f7583.txt +125 -0
  21. package/docs/research/2026-03-20-agents/aab6decea538aac41.txt +148 -0
  22. package/docs/research/2026-03-20-agents/abd58b853dd938a1b.txt +295 -0
  23. package/docs/research/2026-03-20-agents/ac009da573eff7f65.txt +100 -0
  24. package/docs/research/2026-03-20-agents/ac1bc783364405e5f.txt +190 -0
  25. package/docs/research/2026-03-20-agents/aca5e2b57fde152a0.txt +132 -0
  26. package/docs/research/2026-03-20-agents/ad849b8c0a7e95b8b.txt +176 -0
  27. package/docs/research/2026-03-20-agents/adc2b12a4da32c962.txt +258 -0
  28. package/docs/research/2026-03-20-agents/af97caaaa9a80e4cb.txt +146 -0
  29. package/docs/research/2026-03-20-agents/afc5faceee368b3ca.txt +111 -0
  30. package/docs/research/2026-03-20-agents/afdb282d866e3c1e4.txt +164 -0
  31. package/docs/research/2026-03-20-agents/afe9d1f61c02b1e8d.txt +299 -0
  32. package/docs/research/2026-03-20-agents/b4hmkwril.txt +1856 -0
  33. package/docs/research/2026-03-20-agents/b80ptk89g.txt +1856 -0
  34. package/docs/research/2026-03-20-agents/bf54s1jss.txt +1150 -0
  35. package/docs/research/2026-03-20-agents/bhd6kq2kx.txt +1856 -0
  36. package/docs/research/2026-03-20-agents/bmb2fodyr.txt +988 -0
  37. package/docs/research/2026-03-20-agents/bmmsrij8i.txt +826 -0
  38. package/docs/research/2026-03-20-agents/bn4t2ywpu.txt +2175 -0
  39. package/docs/research/2026-03-20-agents/bu22t9f1z.txt +0 -0
  40. package/docs/research/2026-03-20-agents/bwvl98v2p.txt +738 -0
  41. package/docs/research/2026-03-20-agents/psych-a3697a7fd06eb64fd.txt +135 -0
  42. package/docs/research/2026-03-20-agents/psych-a37776fabc870feae.txt +123 -0
  43. package/docs/research/2026-03-20-agents/psych-a5b1fe05c0589efaf.txt +2 -0
  44. package/docs/research/2026-03-20-agents/psych-a95c15b1f29424435.txt +76 -0
  45. package/docs/research/2026-03-20-agents/psych-a9c26f4d9172dde7c.txt +2 -0
  46. package/docs/research/2026-03-20-agents/psych-aa19c69f0ca2c5ad3.txt +2 -0
  47. package/docs/research/2026-03-20-agents/psych-aa4e4cb70e1be5ecb.txt +95 -0
  48. package/docs/research/2026-03-20-agents/psych-ab5b302f26a554663.txt +102 -0
  49. package/docs/research/2026-03-20-deep-research-complete.md +101 -0
  50. package/docs/research/2026-03-20-deep-research-status.md +38 -0
  51. package/docs/research/2026-03-20-enforcement-research.md +107 -0
  52. package/expertise/composition-map.yaml +27 -8
  53. package/expertise/digests/reviewer/ai-coding-digest.md +83 -0
  54. package/expertise/digests/reviewer/architectural-thinking-digest.md +63 -0
  55. package/expertise/digests/reviewer/architecture-antipatterns-digest.md +49 -0
  56. package/expertise/digests/reviewer/code-smells-digest.md +53 -0
  57. package/expertise/digests/reviewer/coupling-cohesion-digest.md +54 -0
  58. package/expertise/digests/reviewer/ddd-digest.md +60 -0
  59. package/expertise/digests/reviewer/dependency-risk-digest.md +40 -0
  60. package/expertise/digests/reviewer/error-handling-digest.md +55 -0
  61. package/expertise/digests/reviewer/review-methodology-digest.md +49 -0
  62. package/exports/hosts/claude/.claude/commands/learn.md +61 -8
  63. package/exports/hosts/claude/.claude/settings.json +7 -6
  64. package/exports/hosts/claude/export.manifest.json +6 -3
  65. package/exports/hosts/claude/host-package.json +3 -0
  66. package/exports/hosts/codex/export.manifest.json +6 -3
  67. package/exports/hosts/codex/host-package.json +3 -0
  68. package/exports/hosts/cursor/.cursor/hooks.json +6 -6
  69. package/exports/hosts/cursor/export.manifest.json +6 -3
  70. package/exports/hosts/cursor/host-package.json +3 -0
  71. package/exports/hosts/gemini/export.manifest.json +6 -3
  72. package/exports/hosts/gemini/host-package.json +3 -0
  73. package/hooks/definitions/pretooluse_dispatcher.yaml +26 -0
  74. package/hooks/definitions/pretooluse_pipeline_guard.yaml +22 -0
  75. package/hooks/definitions/stop_pipeline_gate.yaml +22 -0
  76. package/hooks/hooks.json +7 -6
  77. package/hooks/pretooluse-dispatcher +84 -0
  78. package/hooks/pretooluse-pipeline-guard +9 -0
  79. package/hooks/stop-pipeline-gate +9 -0
  80. package/package.json +2 -2
  81. package/schemas/decision.schema.json +15 -0
  82. package/schemas/hook.schema.json +4 -1
  83. package/skills/TEMPLATE-3-ZONE.md +160 -0
  84. package/skills/brainstorming/SKILL.md +127 -23
  85. package/skills/clarifier/SKILL.md +175 -18
  86. package/skills/claude-cli/SKILL.md +91 -12
  87. package/skills/codex-cli/SKILL.md +91 -12
  88. package/skills/debugging/SKILL.md +133 -38
  89. package/skills/design/SKILL.md +173 -37
  90. package/skills/dispatching-parallel-agents/SKILL.md +129 -31
  91. package/skills/executing-plans/SKILL.md +113 -25
  92. package/skills/executor/SKILL.md +185 -21
  93. package/skills/finishing-a-development-branch/SKILL.md +107 -18
  94. package/skills/gemini-cli/SKILL.md +91 -12
  95. package/skills/humanize/SKILL.md +92 -13
  96. package/skills/init-pipeline/SKILL.md +90 -17
  97. package/skills/prepare-next/SKILL.md +93 -24
  98. package/skills/receiving-code-review/SKILL.md +90 -16
  99. package/skills/requesting-code-review/SKILL.md +100 -24
  100. package/skills/requesting-code-review/code-reviewer.md +29 -17
  101. package/skills/reviewer/SKILL.md +190 -50
  102. package/skills/run-audit/SKILL.md +92 -15
  103. package/skills/scan-project/SKILL.md +93 -14
  104. package/skills/self-audit/SKILL.md +113 -39
  105. package/skills/skill-research/SKILL.md +94 -7
  106. package/skills/subagent-driven-development/SKILL.md +129 -30
  107. package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +30 -2
  108. package/skills/subagent-driven-development/implementer-prompt.md +40 -27
  109. package/skills/subagent-driven-development/spec-reviewer-prompt.md +25 -12
  110. package/skills/tdd/SKILL.md +125 -20
  111. package/skills/using-git-worktrees/SKILL.md +118 -28
  112. package/skills/using-skills/SKILL.md +116 -29
  113. package/skills/verification/SKILL.md +127 -22
  114. package/skills/wazir/SKILL.md +517 -153
  115. package/skills/writing-plans/SKILL.md +134 -28
  116. package/skills/writing-skills/SKILL.md +91 -13
  117. package/skills/writing-skills/anthropic-best-practices.md +104 -64
  118. package/skills/writing-skills/persuasion-principles.md +100 -34
  119. package/tooling/src/capture/command.js +29 -1
  120. package/tooling/src/capture/decision.js +40 -0
  121. package/tooling/src/capture/store.js +1 -0
  122. package/tooling/src/config/depth-table.js +60 -0
  123. package/tooling/src/export/compiler.js +7 -8
  124. package/tooling/src/guards/guardrail-functions.js +131 -0
  125. package/tooling/src/guards/phase-prerequisite-guard.js +39 -3
  126. package/tooling/src/hooks/pretooluse-dispatcher.js +300 -0
  127. package/tooling/src/hooks/pretooluse-pipeline-guard.js +141 -0
  128. package/tooling/src/hooks/stop-pipeline-gate.js +92 -0
  129. package/tooling/src/learn/pipeline.js +177 -0
  130. package/tooling/src/state/db.js +251 -2
  131. package/tooling/src/state/pipeline-state.js +262 -0
  132. package/wazir.manifest.yaml +3 -0
  133. package/workflows/learn.md +61 -8
@@ -0,0 +1,262 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export const PHASE_ORDER = ['init', 'clarify', 'execute', 'verify', 'review', 'complete'];
6
+
7
+ const STATE_FILE = 'pipeline-state.json';
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Atomic file write — temp + rename to prevent corruption
11
+ // ---------------------------------------------------------------------------
12
+
13
+ function atomicWriteJson(filePath, data) {
14
+ const dir = path.dirname(filePath);
15
+ fs.mkdirSync(dir, { recursive: true });
16
+ const tmpPath = `${filePath}.${process.pid}.tmp`;
17
+ fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n', 'utf8');
18
+ fs.renameSync(tmpPath, filePath);
19
+ }
20
+
21
+ function statePath(stateRoot) {
22
+ return path.join(stateRoot, STATE_FILE);
23
+ }
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Read / Create
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * Read the current pipeline state. Returns null if no state file exists.
31
+ */
32
+ export function readPipelineState(stateRoot) {
33
+ const fp = statePath(stateRoot);
34
+ if (!fs.existsSync(fp)) return null;
35
+ try {
36
+ return JSON.parse(fs.readFileSync(fp, 'utf8'));
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Create a fresh pipeline state for a new run.
44
+ */
45
+ export function createPipelineState(runId, stateRoot) {
46
+ const state = {
47
+ run_id: runId,
48
+ current_phase: 'init',
49
+ phase_history: [],
50
+ allowed_transitions: ['clarify'],
51
+ stop_hook_active: false,
52
+ artifacts: {},
53
+ guardrail_results: {},
54
+ session_id: crypto.randomUUID(),
55
+ updated_at: new Date().toISOString(),
56
+ };
57
+ atomicWriteJson(statePath(stateRoot), state);
58
+ return state;
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Transitions
63
+ // ---------------------------------------------------------------------------
64
+
65
+ /**
66
+ * Check whether a transition from currentPhase to nextPhase is valid.
67
+ * Only forward, sequential transitions are allowed.
68
+ */
69
+ export function isTransitionAllowed(currentPhase, nextPhase) {
70
+ const currentIdx = PHASE_ORDER.indexOf(currentPhase);
71
+ const nextIdx = PHASE_ORDER.indexOf(nextPhase);
72
+ if (currentIdx === -1 || nextIdx === -1) return false;
73
+ return nextIdx === currentIdx + 1;
74
+ }
75
+
76
+ /**
77
+ * Transition to the next phase. Validates the transition is legal.
78
+ * Throws on invalid transition or missing state.
79
+ */
80
+ export function transitionPhase(stateRoot, nextPhase) {
81
+ const state = readPipelineState(stateRoot);
82
+ if (!state) {
83
+ throw new Error('No pipeline state found. Call createPipelineState first.');
84
+ }
85
+
86
+ if (!isTransitionAllowed(state.current_phase, nextPhase)) {
87
+ throw new Error(
88
+ `Invalid transition: ${state.current_phase} → ${nextPhase}. ` +
89
+ `Allowed: ${state.current_phase} → ${PHASE_ORDER[PHASE_ORDER.indexOf(state.current_phase) + 1] ?? 'none'}`,
90
+ );
91
+ }
92
+
93
+ const now = new Date().toISOString();
94
+
95
+ // Record the outgoing phase in history
96
+ state.phase_history.push({
97
+ phase: state.current_phase,
98
+ entered_at: state.phase_entered_at ?? state.updated_at,
99
+ exited_at: now,
100
+ status: 'completed',
101
+ });
102
+
103
+ // Move to new phase
104
+ state.current_phase = nextPhase;
105
+ state.phase_entered_at = now;
106
+
107
+ // Compute next allowed transition
108
+ const nextIdx = PHASE_ORDER.indexOf(nextPhase);
109
+ state.allowed_transitions = nextIdx < PHASE_ORDER.length - 1
110
+ ? [PHASE_ORDER[nextIdx + 1]]
111
+ : [];
112
+
113
+ state.updated_at = now;
114
+ atomicWriteJson(statePath(stateRoot), state);
115
+ return state;
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Phase completion (artifact recording)
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * Mark the current phase as having produced artifacts.
124
+ * artifacts: { name: { path: string } }
125
+ */
126
+ export function completePhase(stateRoot, phase, artifacts = {}) {
127
+ const state = readPipelineState(stateRoot);
128
+ if (!state) {
129
+ throw new Error('No pipeline state found.');
130
+ }
131
+
132
+ const now = new Date().toISOString();
133
+
134
+ for (const [name, meta] of Object.entries(artifacts)) {
135
+ const digest = meta.path ? computeArtifactDigest(meta.path) : null;
136
+ state.artifacts[name] = {
137
+ path: meta.path,
138
+ digest,
139
+ created_at: now,
140
+ };
141
+ }
142
+
143
+ state.guardrail_results[phase] = { passed: true, checked_at: now };
144
+ state.updated_at = now;
145
+ atomicWriteJson(statePath(stateRoot), state);
146
+ return state;
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Stop hook flag
151
+ // ---------------------------------------------------------------------------
152
+
153
+ /**
154
+ * Set or clear the stop_hook_active flag to prevent infinite loops.
155
+ */
156
+ export function setStopHookActive(stateRoot, active) {
157
+ const state = readPipelineState(stateRoot);
158
+ if (!state) {
159
+ throw new Error('No pipeline state found.');
160
+ }
161
+ state.stop_hook_active = !!active;
162
+ state.updated_at = new Date().toISOString();
163
+ atomicWriteJson(statePath(stateRoot), state);
164
+ return state;
165
+ }
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Artifact dependency graph
169
+ // ---------------------------------------------------------------------------
170
+
171
+ /**
172
+ * Canonical artifact dependency graph for the pipeline.
173
+ * Each artifact lists the artifacts it requires as inputs.
174
+ */
175
+ export const ARTIFACT_DEPENDENCY_GRAPH = {
176
+ 'clarification.md': { requires: [] },
177
+ 'spec-hardened.md': { requires: ['clarification.md'] },
178
+ 'design.md': { requires: ['spec-hardened.md'] },
179
+ 'execution-plan.md': { requires: ['design.md'] },
180
+ };
181
+
182
+ /**
183
+ * Store artifact dependencies in pipeline state.
184
+ */
185
+ export function setArtifactDependencies(stateRoot, depGraph) {
186
+ const state = readPipelineState(stateRoot);
187
+ if (!state) throw new Error('No pipeline state found.');
188
+ state.artifact_dependencies = depGraph;
189
+ state.updated_at = new Date().toISOString();
190
+ atomicWriteJson(statePath(stateRoot), state);
191
+ return state;
192
+ }
193
+
194
+ /**
195
+ * Compute all artifacts downstream of a changed artifact.
196
+ * Walks the dependency graph to find everything that transitively requires
197
+ * the changed artifact.
198
+ *
199
+ * @param {string} changedArtifact — the artifact that was modified
200
+ * @param {object} depGraph — the dependency graph
201
+ * @returns {string[]} downstream artifact names
202
+ */
203
+ export function computeDownstreamArtifacts(changedArtifact, depGraph) {
204
+ const downstream = [];
205
+ const visited = new Set();
206
+
207
+ function walk(target) {
208
+ for (const [name, meta] of Object.entries(depGraph)) {
209
+ if (visited.has(name)) continue;
210
+ if (meta.requires.includes(target)) {
211
+ visited.add(name);
212
+ downstream.push(name);
213
+ walk(name);
214
+ }
215
+ }
216
+ }
217
+
218
+ walk(changedArtifact);
219
+ return downstream;
220
+ }
221
+
222
+ /**
223
+ * Classify the mutation level of a changed artifact.
224
+ *
225
+ * - L0 (cosmetic): unknown artifact, no graph impact
226
+ * - L1 (local): leaf artifact with no downstream dependents
227
+ * - L2 (structural): mid-graph artifact with some downstream dependents
228
+ * - L3 (fundamental): root artifact — everything downstream is affected
229
+ *
230
+ * @param {string} changedArtifact
231
+ * @param {object} depGraph
232
+ * @returns {'L0'|'L1'|'L2'|'L3'}
233
+ */
234
+ export function classifyMutation(changedArtifact, depGraph) {
235
+ if (!(changedArtifact in depGraph)) return 'L0';
236
+
237
+ const downstream = computeDownstreamArtifacts(changedArtifact, depGraph);
238
+ const entry = depGraph[changedArtifact];
239
+
240
+ // Root artifact (no requirements) with downstream dependents
241
+ if (entry.requires.length === 0 && downstream.length > 0) return 'L3';
242
+
243
+ // Mid-graph: has downstream dependents
244
+ if (downstream.length > 0) return 'L2';
245
+
246
+ // Leaf: no downstream dependents
247
+ return 'L1';
248
+ }
249
+
250
+ // ---------------------------------------------------------------------------
251
+ // Artifact digest
252
+ // ---------------------------------------------------------------------------
253
+
254
+ /**
255
+ * Compute sha256 digest of a file. Returns null if file doesn't exist.
256
+ */
257
+ export function computeArtifactDigest(filePath) {
258
+ if (!fs.existsSync(filePath)) return null;
259
+ const content = fs.readFileSync(filePath);
260
+ const hash = crypto.createHash('sha256').update(content).digest('hex');
261
+ return `sha256:${hash}`;
262
+ }
@@ -97,6 +97,9 @@ required_hooks:
97
97
  - protected_path_write_guard
98
98
  - loop_cap_guard
99
99
  - context_mode_router
100
+ - stop_pipeline_gate
101
+ - pretooluse_pipeline_guard
102
+ - pretooluse_dispatcher
100
103
  protected_paths:
101
104
  - input
102
105
  - roles
@@ -2,37 +2,90 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- Extract durable scoped learnings and experiments from the completed run.
5
+ Extract durable scoped learnings from the completed run using the 4-stage promotion pipeline.
6
6
 
7
7
  ## Phase entry
8
8
 
9
9
  On entering this phase, run:
10
- `wazir capture event --run <run-id> --event phase_enter --phase <phase-name> --status in_progress`
10
+ `wazir capture event --run <run-id> --event phase_enter --phase learn --status in_progress`
11
11
 
12
12
  ## Inputs
13
13
 
14
14
  - run artifacts
15
- - review findings
15
+ - review findings (all passes, all tiers)
16
16
  - verification proof
17
17
 
18
18
  ## Primary Role
19
19
 
20
20
  - `learner`
21
21
 
22
+ ## Pipeline Stages
23
+
24
+ The learning pipeline follows a 4-stage promotion model (Tally → Candidate → Promote → Active):
25
+
26
+ ### Stage 1: TALLY (Automatic)
27
+
28
+ Every finding from the run is:
29
+ 1. Canonicalized (file paths, line numbers, identifiers stripped)
30
+ 2. Hashed for dedup
31
+ 3. Clustered by semantic similarity in `finding_clusters` table
32
+ 4. Category-tagged (from the reviewer's finding category)
33
+
34
+ Also:
35
+ - Read `.wazir/runs/<id>/decisions.ndjson` for recurring patterns
36
+ - Append summary to `memory/findings/cumulative-findings.md`
37
+
38
+ Implementation: `tooling/src/learn/pipeline.js` → `tallyFinding()`
39
+
40
+ ### Stage 2: CANDIDATE (Automatic)
41
+
42
+ Clusters meeting the promotion threshold are flagged:
43
+ - **Occurrence threshold:** 3+ findings with the same canonical pattern
44
+ - **Run threshold:** Pattern must appear across 2+ distinct runs
45
+ - **Drift cap:** No promotion if active antipatterns count >= 30
46
+
47
+ Implementation: `tooling/src/learn/pipeline.js` → `identifyCandidates()` + `promoteToCandidates()`
48
+
49
+ ### Stage 3: PROMOTE (Human Gate)
50
+
51
+ Candidates are proposed for user review:
52
+ - Written to `memory/learnings/proposed/<run-id>-<NNN>.md`
53
+ - User reviews and accepts/rejects via `/wazir audit learnings`
54
+ - Accepted candidates move to `status: accepted` in `antipattern_candidates` table
55
+
56
+ ### Stage 4: ACTIVE (Automatic)
57
+
58
+ Accepted antipatterns are loaded into reviewer context for future runs:
59
+ - Injected as project-level learnings alongside expertise modules
60
+ - Hit-rate tracked: if an antipattern triggers in <5% of runs over 90 days, it's demoted
61
+ - Max 30 active project-level antipatterns (drift prevention)
62
+
63
+ ## Drift Prevention
64
+
65
+ - **Cap:** 30 active project antipatterns maximum
66
+ - **TTL:** 90-day expiry on unreviewed candidates
67
+ - **Demotion:** Antipatterns with <5% hit rate over 90 days are auto-demoted
68
+ - **Consolidation:** When count exceeds 25, similar antipatterns are merged
69
+
22
70
  ## Outputs
23
71
 
24
- - proposed learning artifacts
25
- - experiment summaries
72
+ - Tallied findings in `finding_clusters` table
73
+ - Promoted candidates in `antipattern_candidates` table
74
+ - Proposed learning artifacts in `memory/learnings/proposed/`
75
+ - Cumulative findings appended to `memory/findings/cumulative-findings.md`
26
76
 
27
77
  ## Approval Gate
28
78
 
29
- - accepted learnings require explicit review and scope tags
79
+ - Accepted learnings require explicit user review and scope tags
80
+ - Learnings are NEVER auto-applied to future runs without user acceptance
30
81
 
31
82
  ## Phase exit
32
83
 
33
84
  On completing this phase, run:
34
- `wazir capture event --run <run-id> --event phase_exit --phase <phase-name> --status completed`
85
+ `wazir capture event --run <run-id> --event phase_exit --phase learn --status completed`
35
86
 
36
87
  ## Failure Conditions
37
88
 
38
- - auto-applied learning drift
89
+ - auto-applied learning drift (bypassing human gate)
90
+ - candidate count exceeds cap without consolidation
91
+ - stale candidates not expired