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.
- package/.claude/commands/wogi-bug.md +30 -0
- package/.claude/commands/wogi-debug-hypothesis.md +33 -0
- package/.claude/commands/wogi-morning.md +1 -2
- package/.claude/commands/wogi-review.md +31 -2
- package/.claude/commands/wogi-start.md +32 -0
- package/.claude/commands/wogi-statusline-setup.md +12 -0
- package/.claude/commands/wogi-story.md +3 -2
- package/.claude/docs/claude-code-compatibility.md +40 -0
- package/.claude/docs/phases/01-explore.md +2 -1
- package/.claude/docs/phases/03-implement.md +4 -0
- package/.claude/docs/phases/04-verify.md +45 -0
- package/.claude/rules/README.md +36 -0
- package/.claude/rules/_internal/worker-tool-first-turn.md +82 -0
- package/.claude/rules/alternative-execpolicy-toml-command-policy.md +11 -0
- package/.claude/rules/alternative-hand-edit-ready-json-to-register-orpha.md +11 -0
- package/.claude/rules/alternative-permission-ruleset-per-phase.md +11 -0
- package/.claude/rules/alternative-short-name.md +12 -0
- package/.claude/rules/alternative-wogi-flow-as-mcp-client-oauth-manager.md +11 -0
- package/.claude/rules/architecture/hook-three-layer.md +68 -0
- package/.claude/rules/dual-repo-architecture-2026-02-28.md +18 -0
- package/.claude/rules/github-release-workflow-2026-01-30.md +16 -0
- package/.claude/settings.json +1 -1
- package/.workflow/agents/logic-adversary.md +2 -1
- package/.workflow/agents/personas/README.md +48 -0
- package/.workflow/agents/personas/platform-rigor.md +38 -0
- package/.workflow/agents/personas/scale-skeptic.md +28 -0
- package/.workflow/agents/personas/security-hawk.md +34 -0
- package/.workflow/agents/personas/simplicity-champion.md +37 -0
- package/.workflow/agents/personas/user-advocate.md +36 -0
- package/.workflow/bridges/base-bridge.js +46 -23
- package/.workflow/templates/claude-md.hbs +44 -122
- package/.workflow/templates/partials/feature-dossiers.hbs +33 -0
- package/.workflow/templates/partials/intent-grounded-reasoning.hbs +2 -12
- package/.workflow/templates/partials/methodology-rules.hbs +85 -79
- package/.workflow/templates/tier3-dom-field-inventory.md +102 -0
- package/lib/fuzzy-patch.js +251 -0
- package/lib/installer.js +8 -0
- package/lib/memory-proposal-store.js +458 -0
- package/lib/mode-schema.js +255 -0
- package/lib/skill-proposal-store.js +432 -0
- package/lib/skill-registry.js +1 -1
- package/lib/wogi-claude +84 -9
- package/lib/wogi-claude-expect.exp +113 -76
- package/lib/workspace-channel-server.js +19 -0
- package/lib/workspace-contracts.js +1 -1
- package/lib/workspace-dispatch-tracking.js +144 -0
- package/lib/workspace-gates.js +1 -1
- package/lib/workspace-ipc-sqlite.js +550 -0
- package/lib/workspace-messages.js +92 -0
- package/lib/workspace-routing.js +1 -1
- package/lib/workspace-task-injector.js +223 -0
- package/lib/workspace.js +23 -0
- package/lib/worktree-review.js +315 -0
- package/package.json +2 -2
- package/scripts/base-workflow-step.js +1 -1
- package/scripts/flow +28 -4
- package/scripts/flow-ac-scope-preservation.js +238 -0
- package/scripts/flow-auto-review-worker.js +75 -0
- package/scripts/flow-auto-review.js +102 -0
- package/scripts/flow-autonomous-detector.js +118 -0
- package/scripts/flow-autonomous-mode.js +153 -0
- package/scripts/flow-best-of-n.js +1 -1
- package/scripts/flow-bulk-loop.js +1 -1
- package/scripts/flow-checkpoint.js +2 -6
- package/scripts/flow-community-sync.js +1 -1
- package/scripts/flow-completion-summary.js +176 -0
- package/scripts/flow-completion-truth-gate.js +343 -4
- package/scripts/flow-config-defaults.js +52 -5
- package/scripts/flow-context-compact/expander.js +1 -1
- package/scripts/flow-context-compact/section-extractor.js +2 -2
- package/scripts/flow-context-gatherer.js +1 -1
- package/scripts/flow-context-generator.js +1 -1
- package/scripts/flow-context-scoring.js +1 -1
- package/scripts/flow-correct.js +1 -1
- package/scripts/flow-decision-authority.js +66 -15
- package/scripts/flow-done.js +33 -1
- package/scripts/flow-epic-cascade.js +171 -0
- package/scripts/flow-epics.js +2 -7
- package/scripts/flow-eval-judge.js +1 -1
- package/scripts/flow-eval.js +1 -1
- package/scripts/flow-export-scanner.js +2 -6
- package/scripts/flow-failure-learning.js +1 -1
- package/scripts/flow-feature-dossier.js +787 -0
- package/scripts/flow-figma-extract.js +2 -2
- package/scripts/flow-figma-generate.js +1 -1
- package/scripts/flow-gate-confidence.js +1 -1
- package/scripts/flow-health.js +52 -1
- package/scripts/flow-hooks.js +1 -1
- package/scripts/flow-id.js +19 -3
- package/scripts/flow-instruction-richness.js +1 -1
- package/scripts/flow-knowledge-router.js +1 -1
- package/scripts/flow-knowledge-sync.js +1 -1
- package/scripts/flow-logic-adversary.js +76 -1
- package/scripts/flow-logic-rules.js +380 -0
- package/scripts/flow-long-input.js +5 -5
- package/scripts/flow-memory-sync.js +1 -1
- package/scripts/flow-memory.js +78 -7
- package/scripts/flow-migrate.js +1 -1
- package/scripts/flow-model-caller.js +1 -1
- package/scripts/flow-models.js +2 -2
- package/scripts/flow-morning.js +0 -17
- package/scripts/flow-multi-approach.js +1 -1
- package/scripts/flow-orchestrate-context.js +4 -4
- package/scripts/flow-orchestrate-templates.js +1 -1
- package/scripts/flow-orchestrate.js +8 -8
- package/scripts/flow-peer-review.js +1 -1
- package/scripts/flow-phase.js +9 -0
- package/scripts/flow-proactive-compact.js +1 -1
- package/scripts/flow-providers.js +1 -1
- package/scripts/flow-question-queue.js +255 -0
- package/scripts/flow-repo-map.js +312 -0
- package/scripts/flow-review-passes/index.js +1 -1
- package/scripts/flow-review-passes/integration.js +1 -1
- package/scripts/flow-review-passes/structure.js +1 -1
- package/scripts/flow-revision-tracker.js +1 -1
- package/scripts/flow-section-resolver.js +1 -1
- package/scripts/flow-session-end.js +74 -5
- package/scripts/flow-session-state.js +103 -1
- package/scripts/flow-setup-hooks.js +1 -1
- package/scripts/flow-skeptical-evaluator.js +274 -0
- package/scripts/flow-skill-generator.js +3 -3
- package/scripts/flow-skill-learn.js +3 -6
- package/scripts/flow-skill-manage.js +248 -0
- package/scripts/flow-spec-verifier.js +1 -1
- package/scripts/flow-standards-checker.js +75 -0
- package/scripts/flow-standards-gate.js +1 -1
- package/scripts/flow-statusline-setup.js +8 -2
- package/scripts/flow-step-changelog.js +2 -2
- package/scripts/flow-step-coverage.js +1 -1
- package/scripts/flow-step-knowledge.js +1 -1
- package/scripts/flow-step-regression.js +1 -1
- package/scripts/flow-step-simplifier.js +1 -1
- package/scripts/flow-task-analyzer.js +1 -1
- package/scripts/flow-task-classifier.js +1 -1
- package/scripts/flow-task-enforcer.js +1 -1
- package/scripts/flow-template-extractor.js +1 -1
- package/scripts/flow-trap-zone.js +1 -1
- package/scripts/flow-utils.js +4 -0
- package/scripts/flow-worker-question-classifier.js +51 -5
- package/scripts/flow-workspace-migrate-ipc.js +216 -0
- package/scripts/flow-workspace-summary.js +256 -0
- package/scripts/hooks/adapters/base-adapter.js +2 -2
- package/scripts/hooks/core/feature-dossier-gate.js +194 -0
- package/scripts/hooks/core/observation-capture.js +24 -0
- package/scripts/hooks/core/overdue-dispatches.js +20 -1
- package/scripts/hooks/core/phase-gate.js +15 -1
- package/scripts/hooks/core/phase-transition-auto-review.js +61 -0
- package/scripts/hooks/core/post-compact.js +5 -2
- package/scripts/hooks/core/pre-tool-orchestrator.js +21 -0
- package/scripts/hooks/core/routing-gate.js +58 -0
- package/scripts/hooks/core/session-context.js +108 -0
- package/scripts/hooks/core/session-end-memory-proposals.js +65 -0
- package/scripts/hooks/core/session-end-skill-proposals.js +58 -0
- package/scripts/hooks/core/session-end.js +25 -0
- package/scripts/hooks/core/setup-handler.js +1 -1
- package/scripts/hooks/core/task-boundary-reset.js +110 -4
- package/scripts/hooks/core/worker-boundary-gate.js +71 -0
- package/scripts/hooks/core/worker-tool-first-gate.js +275 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +2 -2
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +7 -2
- package/scripts/hooks/entry/claude-code/session-start.js +74 -30
- package/scripts/hooks/entry/claude-code/stop.js +47 -1
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +17 -0
- 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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
269
|
+
for (const { node: child, includeContent, score: _childScore } of sectionChildren) {
|
|
270
270
|
lines.push(`- ${child.summary}`);
|
|
271
271
|
|
|
272
272
|
if (includeContent && child.content) {
|
|
@@ -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
|
|
179
|
+
function _estimateFileTokens(filePath) {
|
|
180
180
|
try {
|
|
181
181
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
182
182
|
return estimateTokens(content, { useLineEstimate: true });
|
package/scripts/flow-correct.js
CHANGED
|
@@ -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,
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/scripts/flow-done.js
CHANGED
|
@@ -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,
|
|
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
|
+
}
|
package/scripts/flow-epics.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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,
|
package/scripts/flow-eval.js
CHANGED
|
@@ -28,7 +28,7 @@ const {
|
|
|
28
28
|
aggregateScores: _aggregateScores,
|
|
29
29
|
getEvalConfig: _getEvalConfig,
|
|
30
30
|
getJudgeComposition: _getJudgeComposition,
|
|
31
|
-
|
|
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 =
|
|
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
|
};
|