wogiflow 2.26.2 → 2.29.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 (164) hide show
  1. package/.claude/commands/wogi-bug.md +30 -0
  2. package/.claude/commands/wogi-debug-hypothesis.md +33 -0
  3. package/.claude/commands/wogi-morning.md +1 -2
  4. package/.claude/commands/wogi-review.md +31 -2
  5. package/.claude/commands/wogi-start.md +32 -0
  6. package/.claude/commands/wogi-statusline-setup.md +12 -0
  7. package/.claude/commands/wogi-story.md +3 -2
  8. package/.claude/docs/claude-code-compatibility.md +40 -0
  9. package/.claude/docs/phases/01-explore.md +2 -1
  10. package/.claude/docs/phases/03-implement.md +4 -0
  11. package/.claude/docs/phases/04-verify.md +45 -0
  12. package/.claude/rules/README.md +36 -0
  13. package/.claude/rules/_internal/worker-tool-first-turn.md +82 -0
  14. package/.claude/rules/alternative-execpolicy-toml-command-policy.md +11 -0
  15. package/.claude/rules/alternative-hand-edit-ready-json-to-register-orpha.md +11 -0
  16. package/.claude/rules/alternative-permission-ruleset-per-phase.md +11 -0
  17. package/.claude/rules/alternative-short-name.md +12 -0
  18. package/.claude/rules/alternative-wogi-flow-as-mcp-client-oauth-manager.md +11 -0
  19. package/.claude/rules/architecture/hook-three-layer.md +68 -0
  20. package/.claude/rules/dual-repo-architecture-2026-02-28.md +18 -0
  21. package/.claude/rules/github-release-workflow-2026-01-30.md +16 -0
  22. package/.claude/settings.json +1 -1
  23. package/.workflow/agents/logic-adversary.md +2 -1
  24. package/.workflow/agents/personas/README.md +48 -0
  25. package/.workflow/agents/personas/platform-rigor.md +38 -0
  26. package/.workflow/agents/personas/scale-skeptic.md +28 -0
  27. package/.workflow/agents/personas/security-hawk.md +34 -0
  28. package/.workflow/agents/personas/simplicity-champion.md +37 -0
  29. package/.workflow/agents/personas/user-advocate.md +36 -0
  30. package/.workflow/bridges/base-bridge.js +46 -23
  31. package/.workflow/templates/claude-md.hbs +44 -122
  32. package/.workflow/templates/partials/feature-dossiers.hbs +33 -0
  33. package/.workflow/templates/partials/intent-grounded-reasoning.hbs +2 -12
  34. package/.workflow/templates/partials/methodology-rules.hbs +85 -79
  35. package/.workflow/templates/tier3-dom-field-inventory.md +102 -0
  36. package/lib/fuzzy-patch.js +251 -0
  37. package/lib/installer.js +8 -0
  38. package/lib/memory-proposal-store.js +458 -0
  39. package/lib/mode-schema.js +255 -0
  40. package/lib/skill-proposal-store.js +432 -0
  41. package/lib/skill-registry.js +1 -1
  42. package/lib/wogi-claude +84 -9
  43. package/lib/wogi-claude-expect.exp +113 -76
  44. package/lib/workspace-channel-server.js +19 -0
  45. package/lib/workspace-contracts.js +1 -1
  46. package/lib/workspace-dispatch-tracking.js +144 -0
  47. package/lib/workspace-gates.js +1 -1
  48. package/lib/workspace-ipc-sqlite.js +550 -0
  49. package/lib/workspace-messages.js +92 -0
  50. package/lib/workspace-routing.js +1 -1
  51. package/lib/workspace-task-injector.js +223 -0
  52. package/lib/workspace.js +23 -0
  53. package/lib/worktree-review.js +315 -0
  54. package/package.json +2 -2
  55. package/scripts/base-workflow-step.js +1 -1
  56. package/scripts/flow +28 -4
  57. package/scripts/flow-ac-scope-preservation.js +238 -0
  58. package/scripts/flow-auto-review-worker.js +75 -0
  59. package/scripts/flow-auto-review.js +102 -0
  60. package/scripts/flow-autonomous-detector.js +118 -0
  61. package/scripts/flow-autonomous-mode.js +153 -0
  62. package/scripts/flow-best-of-n.js +1 -1
  63. package/scripts/flow-bulk-loop.js +1 -1
  64. package/scripts/flow-checkpoint.js +2 -6
  65. package/scripts/flow-community-sync.js +1 -1
  66. package/scripts/flow-completion-summary.js +176 -0
  67. package/scripts/flow-completion-truth-gate.js +343 -4
  68. package/scripts/flow-config-defaults.js +52 -5
  69. package/scripts/flow-context-compact/expander.js +1 -1
  70. package/scripts/flow-context-compact/section-extractor.js +2 -2
  71. package/scripts/flow-context-gatherer.js +1 -1
  72. package/scripts/flow-context-generator.js +1 -1
  73. package/scripts/flow-context-scoring.js +1 -1
  74. package/scripts/flow-correct.js +1 -1
  75. package/scripts/flow-decision-authority.js +66 -15
  76. package/scripts/flow-done.js +33 -1
  77. package/scripts/flow-epic-cascade.js +171 -0
  78. package/scripts/flow-epics.js +2 -7
  79. package/scripts/flow-eval-judge.js +1 -1
  80. package/scripts/flow-eval.js +1 -1
  81. package/scripts/flow-export-scanner.js +2 -6
  82. package/scripts/flow-failure-learning.js +1 -1
  83. package/scripts/flow-feature-dossier.js +787 -0
  84. package/scripts/flow-figma-extract.js +2 -2
  85. package/scripts/flow-figma-generate.js +1 -1
  86. package/scripts/flow-gate-confidence.js +1 -1
  87. package/scripts/flow-health.js +52 -1
  88. package/scripts/flow-hooks.js +1 -1
  89. package/scripts/flow-id.js +19 -3
  90. package/scripts/flow-instruction-richness.js +1 -1
  91. package/scripts/flow-knowledge-router.js +1 -1
  92. package/scripts/flow-knowledge-sync.js +1 -1
  93. package/scripts/flow-logic-adversary.js +76 -1
  94. package/scripts/flow-logic-rules.js +380 -0
  95. package/scripts/flow-long-input.js +5 -5
  96. package/scripts/flow-memory-sync.js +1 -1
  97. package/scripts/flow-memory.js +78 -7
  98. package/scripts/flow-migrate.js +1 -1
  99. package/scripts/flow-model-caller.js +1 -1
  100. package/scripts/flow-models.js +2 -2
  101. package/scripts/flow-morning.js +0 -17
  102. package/scripts/flow-multi-approach.js +1 -1
  103. package/scripts/flow-orchestrate-context.js +4 -4
  104. package/scripts/flow-orchestrate-templates.js +1 -1
  105. package/scripts/flow-orchestrate.js +8 -8
  106. package/scripts/flow-peer-review.js +1 -1
  107. package/scripts/flow-phase.js +9 -0
  108. package/scripts/flow-proactive-compact.js +1 -1
  109. package/scripts/flow-providers.js +1 -1
  110. package/scripts/flow-question-queue.js +255 -0
  111. package/scripts/flow-repo-map.js +312 -0
  112. package/scripts/flow-review-passes/index.js +1 -1
  113. package/scripts/flow-review-passes/integration.js +1 -1
  114. package/scripts/flow-review-passes/structure.js +1 -1
  115. package/scripts/flow-revision-tracker.js +1 -1
  116. package/scripts/flow-section-resolver.js +1 -1
  117. package/scripts/flow-session-end.js +74 -5
  118. package/scripts/flow-session-state.js +103 -1
  119. package/scripts/flow-setup-hooks.js +1 -1
  120. package/scripts/flow-skeptical-evaluator.js +274 -0
  121. package/scripts/flow-skill-generator.js +3 -3
  122. package/scripts/flow-skill-learn.js +3 -6
  123. package/scripts/flow-skill-manage.js +248 -0
  124. package/scripts/flow-spec-verifier.js +1 -1
  125. package/scripts/flow-standards-checker.js +75 -0
  126. package/scripts/flow-standards-gate.js +1 -1
  127. package/scripts/flow-statusline-setup.js +8 -2
  128. package/scripts/flow-step-changelog.js +2 -2
  129. package/scripts/flow-step-coverage.js +1 -1
  130. package/scripts/flow-step-knowledge.js +1 -1
  131. package/scripts/flow-step-regression.js +1 -1
  132. package/scripts/flow-step-simplifier.js +1 -1
  133. package/scripts/flow-task-analyzer.js +1 -1
  134. package/scripts/flow-task-classifier.js +1 -1
  135. package/scripts/flow-task-enforcer.js +1 -1
  136. package/scripts/flow-template-extractor.js +1 -1
  137. package/scripts/flow-trap-zone.js +1 -1
  138. package/scripts/flow-utils.js +4 -0
  139. package/scripts/flow-worker-question-classifier.js +51 -5
  140. package/scripts/flow-workspace-migrate-ipc.js +216 -0
  141. package/scripts/flow-workspace-summary.js +256 -0
  142. package/scripts/hooks/adapters/base-adapter.js +2 -2
  143. package/scripts/hooks/core/feature-dossier-gate.js +194 -0
  144. package/scripts/hooks/core/observation-capture.js +24 -0
  145. package/scripts/hooks/core/overdue-dispatches.js +20 -1
  146. package/scripts/hooks/core/phase-gate.js +15 -1
  147. package/scripts/hooks/core/phase-transition-auto-review.js +61 -0
  148. package/scripts/hooks/core/post-compact.js +5 -2
  149. package/scripts/hooks/core/pre-tool-orchestrator.js +21 -0
  150. package/scripts/hooks/core/routing-gate.js +58 -0
  151. package/scripts/hooks/core/session-context.js +108 -0
  152. package/scripts/hooks/core/session-end-memory-proposals.js +65 -0
  153. package/scripts/hooks/core/session-end-skill-proposals.js +58 -0
  154. package/scripts/hooks/core/session-end.js +25 -0
  155. package/scripts/hooks/core/setup-handler.js +1 -1
  156. package/scripts/hooks/core/task-boundary-reset.js +110 -4
  157. package/scripts/hooks/core/worker-boundary-gate.js +71 -0
  158. package/scripts/hooks/core/worker-tool-first-gate.js +275 -0
  159. package/scripts/hooks/entry/claude-code/post-tool-use.js +2 -2
  160. package/scripts/hooks/entry/claude-code/pre-tool-use.js +7 -2
  161. package/scripts/hooks/entry/claude-code/session-start.js +74 -30
  162. package/scripts/hooks/entry/claude-code/stop.js +47 -1
  163. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +17 -0
  164. package/.workflow/templates/partials/user-commands.hbs +0 -20
@@ -165,7 +165,9 @@ const CONFIG_DEFAULTS = {
165
165
  projectName: '',
166
166
  cli: {
167
167
  type: 'claude-code',
168
- autoSync: { enabled: false }
168
+ autoSync: { enabled: false },
169
+ generateAgentsMd: true,
170
+ _comment_generateAgentsMd: 'A1 (wf-a346c915): emit AGENTS.md (cross-tool standard used by Codex/Cline/Crush/Aider) alongside CLAUDE.md with identical content. CLAUDE.md remains canonical for drift detection. Set to false to skip AGENTS.md.'
169
171
  },
170
172
  scripts: { lint: null, typecheck: null, test: null, build: null, fix: null, coverage: null },
171
173
 
@@ -415,8 +417,19 @@ const CONFIG_DEFAULTS = {
415
417
  enabled: true,
416
418
  minConfidence: 70,
417
419
  model: 'anthropic:claude-3-5-haiku-latest'
420
+ },
421
+ _comment_toolFirstTurnGate: 'v2.27.0+ (epic wf-34290000, G1+G4+G6): In worker mode, blocks end-of-turn when the current turn (after UserPromptSubmit) had zero tool calls (silent-halt) or — in strict mode — began with a text block before any tool call (text-before-tool-call). Pure-text worker responses are invisible to the user (manager terminal only) and disqualify the three-state end-of-turn contract. Fail-open on transcript/parse/config errors. Set strict:false to allow narrate-then-act patterns (still blocks zero-tool-call turns).',
422
+ toolFirstTurnGate: {
423
+ enabled: true,
424
+ strict: true
418
425
  }
419
426
  },
427
+ _comment_mainModeQuestionClassifier: 'When true, task-boundary-reset runs a Haiku classifier on the final assistant message in main/solo mode before firing the restart. If the message ends by asking the user a question (detected semantically) AND pending-question.json is absent, the classifier auto-writes the marker and defers the restart — preventing the orphaned-question bug when the AI forgot to call `flow ask` manually. Mirrors workspace.aiWorkerQuestionClassifier. Fail-open: no ANTHROPIC_API_KEY / no transcript / model error → restart proceeds normally.',
428
+ mainModeQuestionClassifier: {
429
+ enabled: true,
430
+ minConfidence: 70,
431
+ model: 'anthropic:claude-3-5-haiku-latest'
432
+ },
420
433
  checkpoint: { enabled: false },
421
434
  regressionTesting: { enabled: false },
422
435
 
@@ -706,7 +719,17 @@ const CONFIG_DEFAULTS = {
706
719
  },
707
720
 
708
721
  // --- Session Features ---
709
- morningBriefing: { enabled: false },
722
+ autonomousMode: {
723
+ // Story C / wf-d712002e — values consumed by flow-session-state.js
724
+ stalenessThresholdMs: 60 * 60 * 1000,
725
+ maxAdversaryInvocations: 30,
726
+ maxQueueSize: 100,
727
+ // Story E / wf-e28b6cd8 — cascade-after-decomposition strategy
728
+ // "auto" → restart when autonomous mode is active, direct otherwise
729
+ // "direct" → always same-session Skill invocation (zero latency)
730
+ // "restart" → always SIGTERM+marker cascade (fresh context per story)
731
+ cascadeStrategy: 'auto'
732
+ },
710
733
  techDebt: {
711
734
  enabled: false,
712
735
  promptOnSessionEnd: true,
@@ -825,7 +848,27 @@ const CONFIG_DEFAULTS = {
825
848
  // inherit the full reasoning pipeline. See .claude/docs/intent-grounded-reasoning.md.
826
849
  intentGroundedReasoning: {
827
850
  enabled: true,
828
- _comment: 'IGR pipeline: architect + logic adversary + truth gate. See .claude/docs/intent-grounded-reasoning.md'
851
+ _comment: 'IGR pipeline: architect + logic adversary + truth gate. See .claude/docs/intent-grounded-reasoning.md',
852
+ logicAdversary: {
853
+ personas: {
854
+ enabled: true,
855
+ _comment: 'Persona-library amplifier for Logic Adversary (wf-258f558c). Auto-picks one of scale-skeptic | security-hawk | simplicity-champion | platform-rigor | user-advocate based on plan content. See .workflow/agents/personas/README.md.'
856
+ }
857
+ },
858
+ skepticalEvaluator: {
859
+ enabled: true,
860
+ _comment: 'Validating-phase field-enumeration evaluator (wf-15175dbc). Forces UI-field / API-parameter / state-key enumeration before "done" claims are accepted. See scripts/flow-skeptical-evaluator.js.'
861
+ }
862
+ },
863
+
864
+ // --- Aider-style Repo Map (wf-f3707d2f / C1) ---
865
+ // Auto-generated per task at Step 1 Load Context; refreshed per turn in
866
+ // explore + coding phases. Surfaces TOUCHED / ADJACENT / SHAPE sections
867
+ // within a bounded token budget. See scripts/flow-repo-map.js.
868
+ repoMap: {
869
+ enabled: true,
870
+ budgetBytes: 16384,
871
+ _comment: 'budgetBytes ≈ 4K tokens; raise for large tasks with many touched files, lower for cheap-model executors.'
829
872
  },
830
873
 
831
874
  // --- Research Reasoning Gate ---
@@ -930,7 +973,9 @@ const CONFIG_DEFAULTS = {
930
973
  _comment: 'Per-task context reset via wogi-claude wrapper. See lib/wogi-claude.',
931
974
  enabled: true,
932
975
  maxRestartsPerSession: 50,
933
- _comment_maxRestartsPerSession: 'Safety cap. The wrapper also has WOGI_MAX_RESTARTS env override.'
976
+ _comment_maxRestartsPerSession: 'Safety cap. The wrapper also has WOGI_MAX_RESTARTS env override.',
977
+ autoPickupNextTask: true,
978
+ _comment_autoPickupNextTask: 'When true, after a clean task completion + restart, the next SessionStart injects an AUTO-PICKUP block instructing the AI to immediately invoke /wogi-start <nextReadyId> instead of asking "what\'s next?". Skipped if pending-question.json exists, ready queue is empty, or this flag is false.'
934
979
  },
935
980
 
936
981
  // --- Contract Surface (Teams-only — activated on wogi login) ---
@@ -1018,6 +1063,8 @@ const CONFIG_DEFAULTS = {
1018
1063
  },
1019
1064
  peer: { enabled: false },
1020
1065
  triage: { enabled: false },
1066
+ evidenceTiers: { enabled: true, capByTier: true },
1067
+ confidenceTiers: { enabled: true, rubricPath: '.workflow/rubrics/confidence-tiers.md' },
1021
1068
  agents: REVIEW_AGENTS
1022
1069
  },
1023
1070
 
@@ -1309,7 +1356,7 @@ function applyProjectTypeDefaults(config) {
1309
1356
  const detected = config.testing?.detected;
1310
1357
  if (!detected || !detected.projectType) return config;
1311
1358
 
1312
- const { hasUI, hasAPI, projectType } = detected;
1359
+ const { hasUI, hasAPI, _projectType } = detected;
1313
1360
 
1314
1361
  // --- Adjust testing mode default ---
1315
1362
  if (config.testing?.mode === 'auto') {
@@ -181,7 +181,7 @@ function expandForQuery(query, options = {}) {
181
181
  const lines = [];
182
182
  let totalTokens = 0;
183
183
 
184
- for (const { id, node, tokens, score } of toExpand) {
184
+ for (const { id, node, tokens, _score } of toExpand) {
185
185
  markExpanded(id, tokens, `query: ${query.substring(0, 50)}`);
186
186
 
187
187
  lines.push(`### ${node.summary}`);
@@ -157,7 +157,7 @@ function selectNodes(tree, options = {}) {
157
157
  sectionScores.sort((a, b) => b.score - a.score);
158
158
 
159
159
  // Include sections based on score and budget
160
- for (const { section, score, id } of sectionScores) {
160
+ for (const { section, score, _id } of sectionScores) {
161
161
  // Always include certain types
162
162
  const alwaysInclude = alwaysIncludeTypes.includes(section.type);
163
163
 
@@ -266,7 +266,7 @@ function formatSelectedContext(tree, selectedNodes) {
266
266
  node.children?.includes(d.node.id)
267
267
  );
268
268
 
269
- for (const { node: child, includeContent, score: childScore } of sectionChildren) {
269
+ for (const { node: child, includeContent, score: _childScore } of sectionChildren) {
270
270
  lines.push(`- ${child.summary}`);
271
271
 
272
272
  if (includeContent && child.content) {
@@ -34,7 +34,7 @@ const {
34
34
  estimateTokens,
35
35
  info,
36
36
  warn: _warn,
37
- success
37
+ _success
38
38
  } = require('./flow-utils');
39
39
 
40
40
  const {
@@ -633,7 +633,7 @@ function generateImportsMarkdown(imports) {
633
633
  '```typescript'
634
634
  ];
635
635
 
636
- for (const [name, statement] of Object.entries(imports)) {
636
+ for (const [_name, statement] of Object.entries(imports)) {
637
637
  lines.push(statement);
638
638
  }
639
639
 
@@ -176,7 +176,7 @@ function categorizeScore(score) {
176
176
  * @param {string} filePath - Path to file
177
177
  * @returns {number} Estimated tokens
178
178
  */
179
- function estimateFileTokens(filePath) {
179
+ function _estimateFileTokens(filePath) {
180
180
  try {
181
181
  const content = fs.readFileSync(filePath, 'utf8');
182
182
  return estimateTokens(content, { useLineEstimate: true });
@@ -169,7 +169,7 @@ _Generated by \`flow correct\`_
169
169
  /**
170
170
  * Update feedback-patterns.md with the correction
171
171
  */
172
- function updateFeedbackPatterns(brief, _taskId, skillName) {
172
+ function updateFeedbackPatterns(brief, _taskId, _skillName) {
173
173
  const date = getTodayDate();
174
174
 
175
175
  let content = readFile(PATHS.feedbackPatterns, '');
@@ -31,7 +31,10 @@ const AUTHORITY_LEVELS = {
31
31
  'agent-decides': 'Agent decides autonomously, reports in completion summary',
32
32
  'agent-decides-report-after': 'Agent decides, explicitly reports the decision after',
33
33
  'owner-decides': 'Present to user, wait for answer before proceeding',
34
- 'auto-fix-report-after': 'Agent fixes automatically, reports what was fixed'
34
+ 'auto-fix-report-after': 'Agent fixes automatically, reports what was fixed',
35
+ // Autonomous-mode buckets (Story C / wf-d712002e)
36
+ 'queue-for-review': 'Autonomous mode: append to question queue, render in completion summary',
37
+ 'adversary-loop': 'Autonomous mode: invoke self-adversarial challenge, then queue if still <90% confidence'
35
38
  };
36
39
 
37
40
  const DEFAULT_AUTHORITY_CONFIG = {
@@ -122,16 +125,55 @@ function getAuthorityConfig() {
122
125
  }
123
126
 
124
127
  /**
125
- * Classify a decision text into a category
128
+ * Resolve the autonomous flag. Caller may pass an explicit boolean to override
129
+ * the session-state lookup (used by tests and by call sites that already know
130
+ * the flag).
131
+ *
132
+ * Default: read from session-state cache via flow-session-state. The cache is
133
+ * populated by SessionStart rehydration, so hot-path callers do not pay for
134
+ * a disk read.
135
+ */
136
+ function resolveAutonomous(autonomousArg) {
137
+ if (typeof autonomousArg === 'boolean') return autonomousArg;
138
+ try {
139
+ const { isAutonomousActive } = require('./flow-session-state');
140
+ return isAutonomousActive();
141
+ } catch (_err) {
142
+ return false;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Apply autonomous-mode overrides to a base authority decision.
148
+ * Applied AFTER the base classification so that batch-overflow only fires for
149
+ * decisions the autonomous override did not handle (Blocker 1 fix).
150
+ */
151
+ function applyAutonomousOverride(baseAuthority, { category, confidence }) {
152
+ if (confidence === 'low') {
153
+ return 'adversary-loop';
154
+ }
155
+ if (baseAuthority === 'owner-decides') {
156
+ if (category === 'productBehavior' || category === 'ux') {
157
+ return 'queue-for-review';
158
+ }
159
+ }
160
+ return baseAuthority;
161
+ }
162
+
163
+ /**
164
+ * Classify a decision text into a category.
165
+ *
126
166
  * @param {string} decisionText - The decision to classify
167
+ * @param {object} [options]
168
+ * @param {boolean} [options.autonomous] - Force autonomous mode on/off. When
169
+ * omitted, reads from session-state cache.
127
170
  * @returns {{ category: string, authority: string, confidence: string }}
128
171
  */
129
- function classifyDecision(decisionText) {
172
+ function classifyDecision(decisionText, options = {}) {
130
173
  const authorityConfig = getAuthorityConfig();
131
- // Patterns already use /i flag, no need for toLowerCase()
174
+ const autonomous = resolveAutonomous(options.autonomous);
132
175
  const text = decisionText;
133
176
 
134
- // Score each category
135
177
  const scores = {};
136
178
  for (const [category, patterns] of Object.entries(CATEGORY_PATTERNS)) {
137
179
  scores[category] = 0;
@@ -142,8 +184,7 @@ function classifyDecision(decisionText) {
142
184
  }
143
185
  }
144
186
 
145
- // Find highest scoring category
146
- let bestCategory = 'productBehavior'; // Default to owner-decides (safest)
187
+ let bestCategory = 'productBehavior';
147
188
  let bestScore = 0;
148
189
 
149
190
  for (const [category, score] of Object.entries(scores)) {
@@ -153,21 +194,25 @@ function classifyDecision(decisionText) {
153
194
  }
154
195
  }
155
196
 
156
- // Determine confidence
157
197
  const totalMatches = Object.values(scores).reduce((a, b) => a + b, 0);
158
198
  let confidence = 'low';
159
199
  if (bestScore >= 3) confidence = 'high';
160
200
  else if (bestScore >= 2) confidence = 'medium';
161
201
  else if (bestScore === 1 && totalMatches <= 2) confidence = 'medium';
162
202
 
163
- // Low-confidence decisions default to owner-decides for safety
164
- const authority = confidence === 'low'
203
+ const baseAuthority = confidence === 'low'
165
204
  ? 'owner-decides'
166
205
  : (authorityConfig[bestCategory] ?? 'owner-decides');
167
206
 
207
+ const authority = autonomous
208
+ ? applyAutonomousOverride(baseAuthority, { category: bestCategory, confidence })
209
+ : baseAuthority;
210
+
168
211
  return {
169
212
  category: bestCategory,
170
213
  authority,
214
+ baseAuthority,
215
+ autonomous,
171
216
  confidence,
172
217
  score: bestScore,
173
218
  description: AUTHORITY_LEVELS[authority] ?? 'Unknown authority level'
@@ -179,25 +224,26 @@ function classifyDecision(decisionText) {
179
224
  * @param {string[]} decisions - Array of decision texts
180
225
  * @returns {{ classified: Object[], ownerQuestions: Object[], agentDecisions: Object[], truncated: boolean }}
181
226
  */
182
- function batchClassify(decisions) {
227
+ function batchClassify(decisions, options = {}) {
183
228
  const authorityConfig = getAuthorityConfig();
184
229
  const maxOwner = authorityConfig.maxOwnerQuestionsPerBatch ?? 5;
230
+ const autonomous = resolveAutonomous(options.autonomous);
185
231
 
232
+ // Autonomous routing is applied per-decision FIRST (Blocker 1 fix).
233
+ // Batch-overflow only fires for decisions still routed to owner-decides
234
+ // after the autonomous override pass.
186
235
  const classified = decisions.map((text, idx) => ({
187
236
  index: idx,
188
237
  text,
189
- ...classifyDecision(text)
238
+ ...classifyDecision(text, { autonomous })
190
239
  }));
191
240
 
192
- // Separate owner-decides from agent-decides
193
241
  const ownerQuestions = classified.filter(d => d.authority === 'owner-decides');
194
242
  const agentDecisions = classified.filter(d => d.authority !== 'owner-decides');
195
243
 
196
- // Enforce max owner questions — overflow becomes agent-decides-report-after
197
244
  let truncated = false;
198
245
  if (ownerQuestions.length > maxOwner) {
199
246
  truncated = true;
200
- // Keep the first maxOwner, downgrade the rest
201
247
  const overflow = ownerQuestions.splice(maxOwner);
202
248
  for (const decision of overflow) {
203
249
  decision.authority = 'agent-decides-report-after';
@@ -214,10 +260,13 @@ function batchClassify(decisions) {
214
260
  agentDecisions,
215
261
  truncated,
216
262
  maxOwner,
263
+ autonomous,
217
264
  stats: {
218
265
  total: decisions.length,
219
266
  ownerDecides: ownerQuestions.length,
220
267
  agentDecides: agentDecisions.length,
268
+ queued: classified.filter(d => d.authority === 'queue-for-review').length,
269
+ adversaryLoop: classified.filter(d => d.authority === 'adversary-loop').length,
221
270
  downgraded: truncated ? agentDecisions.filter(d => d.downgraded).length : 0
222
271
  }
223
272
  };
@@ -349,6 +398,8 @@ module.exports = {
349
398
  batchClassify,
350
399
  getAuthorityConfig,
351
400
  updateCategoryAuthority,
401
+ applyAutonomousOverride,
402
+ resolveAutonomous,
352
403
  AUTHORITY_LEVELS,
353
404
  DEFAULT_AUTHORITY_CONFIG,
354
405
  CATEGORY_PATTERNS
@@ -74,7 +74,7 @@ const {
74
74
  allStoriesComplete: _allStoriesComplete, allFeaturesComplete: _allFeaturesComplete, allEpicsComplete: _allEpicsComplete,
75
75
  markFeatureComplete: _markFeatureComplete, markEpicComplete: _markEpicComplete, markPlanComplete: _markPlanComplete,
76
76
  archiveByType: _archiveByType, archiveCompletedParent: _archiveCompletedParent, cascadeCompletion,
77
- CASCADE_MAX_DEPTH: _CASCADE_MAX_DEPTH, VALID_CASCADE_TYPES
77
+ CASCADE_MAX_DEPTH: _CASCADE_MAX_DEPTH, _VALID_CASCADE_TYPES
78
78
  } = require('./flow-cascade-completion');
79
79
 
80
80
  // v3.1 spec verification gate
@@ -715,6 +715,38 @@ async function main() {
715
715
  if (process.env.DEBUG) console.error(`[DEBUG] Clarification learning: ${err.message}`);
716
716
  }
717
717
 
718
+ // Auto-touch feature dossiers matching this completed task.
719
+ // Appends a Change Log row to every dossier whose match-patterns match
720
+ // the task title/description/files. Fail-open — never blocks completion.
721
+ try {
722
+ const { autoTouchFromTask } = require('./flow-feature-dossier');
723
+ // Widen window to catch multi-commit tasks. execFileSync avoids shell
724
+ // interpretation. Union of recent commits + uncommitted working tree.
725
+ const fileSet = new Set();
726
+ try {
727
+ const commits = execFileSync('git', ['log', '--name-only', '--pretty=format:', '-n', '10'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] });
728
+ commits.split('\n').map(s => s.trim()).filter(Boolean).forEach(f => fileSet.add(f));
729
+ } catch (_err) { /* no git / no history */ }
730
+ try {
731
+ const dirty = execFileSync('git', ['diff', '--name-only', 'HEAD'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] });
732
+ dirty.split('\n').map(s => s.trim()).filter(Boolean).forEach(f => fileSet.add(f));
733
+ } catch (_err) { /* noop */ }
734
+
735
+ const touchResult = autoTouchFromTask({
736
+ taskId,
737
+ title: result.task?.title || '',
738
+ description: result.task?.description || '',
739
+ type: result.task?.type || 'feat',
740
+ files: [...fileSet]
741
+ });
742
+ if (touchResult.touched && touchResult.touched.length > 0) {
743
+ const slugs = touchResult.touched.map(t => t.slug).join(', ');
744
+ console.log(color('cyan', `📒 Updated feature dossiers: ${slugs}`));
745
+ }
746
+ } catch (err) {
747
+ if (process.env.DEBUG) console.error(`[DEBUG] dossier auto-touch: ${err.message}`);
748
+ }
749
+
718
750
  // v1.7.0: Track task completion in session state and memory blocks
719
751
  // v3.2.1: Improved error handling - don't silently swallow failures
720
752
  try {
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow — Epic Decompose-and-Run Cascade (Story E / wf-e28b6cd8)
5
+ *
6
+ * After /wogi-start <epicId> completes decomposition, this helper picks
7
+ * what happens next:
8
+ *
9
+ * Option A — direct same-session invocation
10
+ * Returns `{ action: 'invoke-skill', taskId: <firstChild> }`. The
11
+ * wogi-start prompt then immediately calls
12
+ * Skill(skill="wogi-start", args=<firstChild>) in the SAME turn.
13
+ * Pro: zero latency. Con: epic + first-story share context.
14
+ *
15
+ * Option B — clean-completion-marker cascade (restart)
16
+ * Writes the marker file with `nextTaskId` set, returns
17
+ * `{ action: 'restart-with-marker' }`. SessionStart's AUTO-PICKUP
18
+ * block reads the marker and starts the first child in a fresh
19
+ * session. Pro: fresh context per story. Con: restart latency.
20
+ *
21
+ * None — abort
22
+ * `{ action: 'abort', reason }`. Caller emits a warning, ends turn.
23
+ *
24
+ * Strategy resolution:
25
+ * - autonomousMode.cascadeStrategy: "auto" | "direct" | "restart"
26
+ * - "auto" (default) → restart when autonomous mode active, direct
27
+ * otherwise.
28
+ * - "direct" → always Option A.
29
+ * - "restart" → always Option B.
30
+ *
31
+ * Edge cases (Phase 4 of spec):
32
+ * 1. Epic decomposes to 0 stories → action='abort', reason='no-children'
33
+ * 2. Epic decomposes to 1 story → cascade normally
34
+ * 3. Epic has pre-existing child stories → no decomp, cascade to first
35
+ * 4. Worker mode → same mechanism (ready.json is member-repo-aware)
36
+ * 5. Cascade target missing/malformed → action='abort', reason='target-missing'
37
+ *
38
+ * Programmatic:
39
+ * const cascade = require('./flow-epic-cascade');
40
+ * const result = cascade.resolveCascade({ epicId });
41
+ * // result: { action: 'invoke-skill' | 'restart-with-marker' | 'abort', taskId?, reason? }
42
+ *
43
+ * Note on AC1 (latency measurement): the spec calls for a 10-cycle
44
+ * wall-clock measurement of the SIGTERM/relaunch cycle. That cannot be
45
+ * meaningfully measured from inside a Node script — it requires
46
+ * instrumenting the wogi-claude wrapper across actual restarts. Captured
47
+ * as runtime-deferred; tracked under future test infrastructure.
48
+ */
49
+
50
+ const { getConfig, getReadyData } = require('./flow-utils');
51
+ const { writeCleanCompletionMarker } = require('./hooks/core/task-boundary-reset');
52
+
53
+ const VALID_STRATEGIES = new Set(['auto', 'direct', 'restart']);
54
+
55
+ function getCascadeStrategy() {
56
+ const cfg = getConfig().autonomousMode || {};
57
+ const raw = cfg.cascadeStrategy;
58
+ if (typeof raw === 'string' && VALID_STRATEGIES.has(raw)) return raw;
59
+ return 'auto';
60
+ }
61
+
62
+ function findEpicInQueue(epicId) {
63
+ const data = getReadyData();
64
+ const allLists = [data.ready, data.inProgress, data.blocked, data.recentlyCompleted].filter(Array.isArray);
65
+ for (const list of allLists) {
66
+ const found = list.find(t => t && t.id === epicId);
67
+ if (found) return found;
68
+ }
69
+ return null;
70
+ }
71
+
72
+ /**
73
+ * Resolve the first child story for an epic from the ready queue (children
74
+ * land in `data.ready` after decomposition). Skips epics; respects natural
75
+ * order so the first appended child wins.
76
+ *
77
+ * @param {string} epicId
78
+ * @returns {{taskId:string,title:string}|null}
79
+ */
80
+ function resolveFirstChildStory(epicId) {
81
+ const data = getReadyData();
82
+ const queue = Array.isArray(data.ready) ? data.ready : [];
83
+ const candidate = queue.find(t =>
84
+ t && t.id !== epicId && t.parentEpic === epicId && t.type !== 'epic'
85
+ );
86
+ if (candidate) {
87
+ return { taskId: candidate.id, title: candidate.title || null };
88
+ }
89
+ // Fallback: read epic's stories array if parentEpic isn't set.
90
+ const epic = findEpicInQueue(epicId);
91
+ if (epic && Array.isArray(epic.stories) && epic.stories.length) {
92
+ for (const childId of epic.stories) {
93
+ const child = queue.find(t => t && t.id === childId && t.type !== 'epic');
94
+ if (child) return { taskId: child.id, title: child.title || null };
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+
100
+ /**
101
+ * Decide and (for restart strategy) write the marker.
102
+ *
103
+ * @param {object} input
104
+ * @param {string} input.epicId
105
+ * @param {boolean} [input.autonomousActive] - Override the live session
106
+ * flag (used by tests). Defaults to flow-session-state.isAutonomousActive().
107
+ * @returns {{action:string, taskId?:string, title?:string, reason?:string, strategy:string}}
108
+ */
109
+ function resolveCascade({ epicId, autonomousActive, strategy: strategyOverride } = {}) {
110
+ if (!epicId) {
111
+ return { action: 'abort', reason: 'no-epic-id', strategy: 'auto' };
112
+ }
113
+
114
+ const child = resolveFirstChildStory(epicId);
115
+ if (!child) {
116
+ return { action: 'abort', reason: 'no-children', strategy: 'auto' };
117
+ }
118
+
119
+ const strategy = (typeof strategyOverride === 'string' && VALID_STRATEGIES.has(strategyOverride))
120
+ ? strategyOverride
121
+ : getCascadeStrategy();
122
+ let mode;
123
+ if (strategy === 'direct') {
124
+ mode = 'invoke-skill';
125
+ } else if (strategy === 'restart') {
126
+ mode = 'restart-with-marker';
127
+ } else {
128
+ let isAuto = autonomousActive;
129
+ if (typeof isAuto !== 'boolean') {
130
+ try {
131
+ isAuto = require('./flow-session-state').isAutonomousActive();
132
+ } catch (_err) { isAuto = false; }
133
+ }
134
+ mode = isAuto ? 'restart-with-marker' : 'invoke-skill';
135
+ }
136
+
137
+ if (mode === 'restart-with-marker') {
138
+ try {
139
+ writeCleanCompletionMarker(epicId, `Epic decomposed; cascade to ${child.taskId}`, {
140
+ nextTaskId: child.taskId
141
+ });
142
+ } catch (err) {
143
+ return {
144
+ action: 'abort',
145
+ reason: `marker-write-failed: ${err.message}`,
146
+ strategy
147
+ };
148
+ }
149
+ }
150
+
151
+ return { action: mode, taskId: child.taskId, title: child.title, strategy };
152
+ }
153
+
154
+ module.exports = {
155
+ getCascadeStrategy,
156
+ resolveFirstChildStory,
157
+ resolveCascade,
158
+ VALID_STRATEGIES: [...VALID_STRATEGIES]
159
+ };
160
+
161
+ if (require.main === module) {
162
+ const [,, cmd, arg] = process.argv;
163
+ if (cmd === 'resolve') {
164
+ const r = resolveCascade({ epicId: arg });
165
+ console.log(JSON.stringify(r, null, 2));
166
+ } else if (cmd === 'strategy') {
167
+ console.log(getCascadeStrategy());
168
+ } else {
169
+ console.log('Usage: flow-epic-cascade <resolve <epicId>|strategy>');
170
+ }
171
+ }
@@ -79,10 +79,6 @@ function saveEpicsState(state) {
79
79
  * Load ready.json for task data
80
80
  * @returns {Object} Ready data
81
81
  */
82
- // loadReadyData replaced by canonical getReadyData from flow-utils (wf-7072d3ac / dup-007).
83
- // Kept as a thin shim for backward-compat of the module export at L812.
84
- function loadReadyData() { return getReadyData(); }
85
-
86
82
  // ============================================================
87
83
  // Epic File Operations
88
84
  // ============================================================
@@ -570,7 +566,7 @@ function updateEpicProgress(epicId) {
570
566
  return { error: `Epic ${epicId} not found` };
571
567
  }
572
568
 
573
- const readyData = loadReadyData();
569
+ const readyData = getReadyData();
574
570
 
575
571
  let totalWeight = 0;
576
572
  let completedWeight = 0;
@@ -635,7 +631,7 @@ function buildHierarchyTree(epicId) {
635
631
  const epic = getEpic(epicId);
636
632
  if (!epic) return null;
637
633
 
638
- const readyData = loadReadyData();
634
+ const readyData = getReadyData();
639
635
 
640
636
  const tree = {
641
637
  ...epic,
@@ -803,7 +799,6 @@ module.exports = {
803
799
  // State management
804
800
  loadEpicsState,
805
801
  saveEpicsState,
806
- loadReadyData,
807
802
 
808
803
  // Epic operations
809
804
  createEpic,
@@ -23,7 +23,7 @@ const {
23
23
  readJson: _readJson,
24
24
  writeJson: _writeJson,
25
25
  fileExists: _fileExists,
26
- safeJsonParse
26
+ _safeJsonParse
27
27
  } = require('./flow-utils');
28
28
 
29
29
  // ============================================================
@@ -28,7 +28,7 @@ const {
28
28
  aggregateScores: _aggregateScores,
29
29
  getEvalConfig: _getEvalConfig,
30
30
  getJudgeComposition: _getJudgeComposition,
31
- formatEvalResults
31
+ _formatEvalResults
32
32
  } = require('./flow-eval-judge');
33
33
 
34
34
  // ============================================================
@@ -43,9 +43,6 @@ function getProjectRoot() {
43
43
  return PROJECT_ROOT;
44
44
  }
45
45
 
46
- // Alias getConfig as loadConfig for minimal code changes
47
- const loadConfig = getConfig;
48
-
49
46
  // ============================================================
50
47
  // Export Extraction
51
48
  // ============================================================
@@ -996,7 +993,7 @@ if (require.main === module) {
996
993
 
997
994
  if (!exportMap) {
998
995
  console.log('Scanning project exports...\n');
999
- const config = loadConfig();
996
+ const config = getConfig();
1000
997
  exportMap = buildExportMap(config);
1001
998
  saveExportMapCache(exportMap);
1002
999
  }
@@ -1041,6 +1038,5 @@ module.exports = {
1041
1038
  formatComponentWithUsage,
1042
1039
  // Configuration functions (for use as module)
1043
1040
  setProjectRoot,
1044
- getProjectRoot,
1045
- loadConfig
1041
+ getProjectRoot
1046
1042
  };
@@ -34,7 +34,7 @@ const {
34
34
  info,
35
35
  warn,
36
36
  success: _success,
37
- error: logError,
37
+ error: _logError,
38
38
  parseFlags,
39
39
  outputJson,
40
40
  safeJsonParse