wogiflow 2.26.2 → 2.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) 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 +149 -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-config-loader.js +3 -2
  70. package/scripts/flow-context-compact/expander.js +1 -1
  71. package/scripts/flow-context-compact/section-extractor.js +2 -2
  72. package/scripts/flow-context-gatherer.js +1 -1
  73. package/scripts/flow-context-generator.js +1 -1
  74. package/scripts/flow-context-scoring.js +1 -1
  75. package/scripts/flow-correct.js +1 -1
  76. package/scripts/flow-correction-detector.js +3 -2
  77. package/scripts/flow-decision-authority.js +66 -15
  78. package/scripts/flow-done.js +33 -1
  79. package/scripts/flow-epic-cascade.js +171 -0
  80. package/scripts/flow-epics.js +2 -7
  81. package/scripts/flow-eval-judge.js +1 -1
  82. package/scripts/flow-eval.js +1 -1
  83. package/scripts/flow-export-scanner.js +2 -6
  84. package/scripts/flow-failure-learning.js +1 -1
  85. package/scripts/flow-feature-dossier.js +787 -0
  86. package/scripts/flow-figma-extract.js +2 -2
  87. package/scripts/flow-figma-generate.js +1 -1
  88. package/scripts/flow-gate-confidence.js +1 -1
  89. package/scripts/flow-health.js +52 -1
  90. package/scripts/flow-hooks.js +1 -1
  91. package/scripts/flow-id.js +19 -3
  92. package/scripts/flow-instruction-richness.js +1 -1
  93. package/scripts/flow-knowledge-router.js +1 -1
  94. package/scripts/flow-knowledge-sync.js +1 -1
  95. package/scripts/flow-logic-adversary.js +76 -1
  96. package/scripts/flow-logic-rules.js +380 -0
  97. package/scripts/flow-long-input.js +5 -5
  98. package/scripts/flow-memory-sync.js +1 -1
  99. package/scripts/flow-memory.js +78 -7
  100. package/scripts/flow-migrate.js +1 -1
  101. package/scripts/flow-model-caller.js +1 -1
  102. package/scripts/flow-models.js +2 -2
  103. package/scripts/flow-morning.js +0 -17
  104. package/scripts/flow-multi-approach.js +1 -1
  105. package/scripts/flow-orchestrate-context.js +4 -4
  106. package/scripts/flow-orchestrate-templates.js +1 -1
  107. package/scripts/flow-orchestrate.js +8 -8
  108. package/scripts/flow-peer-review.js +1 -1
  109. package/scripts/flow-phase.js +9 -0
  110. package/scripts/flow-proactive-compact.js +1 -1
  111. package/scripts/flow-prompt-composer.js +3 -2
  112. package/scripts/flow-prompt-template.js +3 -2
  113. package/scripts/flow-providers.js +1 -1
  114. package/scripts/flow-question-queue.js +255 -0
  115. package/scripts/flow-repo-map.js +312 -0
  116. package/scripts/flow-review-passes/index.js +1 -1
  117. package/scripts/flow-review-passes/integration.js +1 -1
  118. package/scripts/flow-review-passes/structure.js +1 -1
  119. package/scripts/flow-revision-tracker.js +1 -1
  120. package/scripts/flow-section-resolver.js +1 -1
  121. package/scripts/flow-session-end.js +74 -5
  122. package/scripts/flow-session-state.js +103 -1
  123. package/scripts/flow-setup-hooks.js +1 -1
  124. package/scripts/flow-skeptical-evaluator.js +274 -0
  125. package/scripts/flow-skill-generator.js +3 -3
  126. package/scripts/flow-skill-learn.js +3 -6
  127. package/scripts/flow-skill-manage.js +248 -0
  128. package/scripts/flow-spec-verifier.js +1 -1
  129. package/scripts/flow-standards-checker.js +75 -0
  130. package/scripts/flow-standards-gate.js +1 -1
  131. package/scripts/flow-statusline-setup.js +8 -2
  132. package/scripts/flow-step-changelog.js +2 -2
  133. package/scripts/flow-step-coverage.js +1 -1
  134. package/scripts/flow-step-knowledge.js +1 -1
  135. package/scripts/flow-step-regression.js +1 -1
  136. package/scripts/flow-step-simplifier.js +1 -1
  137. package/scripts/flow-task-analyzer.js +1 -1
  138. package/scripts/flow-task-classifier.js +1 -1
  139. package/scripts/flow-task-enforcer.js +1 -1
  140. package/scripts/flow-template-extractor.js +1 -1
  141. package/scripts/flow-trap-zone.js +1 -1
  142. package/scripts/flow-utils.js +4 -0
  143. package/scripts/flow-worker-mcp-strip.js +122 -0
  144. package/scripts/flow-worker-question-classifier.js +51 -5
  145. package/scripts/flow-workspace-migrate-ipc.js +216 -0
  146. package/scripts/flow-workspace-summary.js +256 -0
  147. package/scripts/hooks/adapters/base-adapter.js +2 -2
  148. package/scripts/hooks/core/feature-dossier-gate.js +194 -0
  149. package/scripts/hooks/core/observation-capture.js +24 -0
  150. package/scripts/hooks/core/overdue-dispatches.js +20 -1
  151. package/scripts/hooks/core/phase-gate.js +15 -1
  152. package/scripts/hooks/core/phase-transition-auto-review.js +61 -0
  153. package/scripts/hooks/core/post-compact.js +5 -2
  154. package/scripts/hooks/core/pre-tool-orchestrator.js +21 -0
  155. package/scripts/hooks/core/routing-gate.js +58 -0
  156. package/scripts/hooks/core/session-context.js +108 -0
  157. package/scripts/hooks/core/session-end-memory-proposals.js +65 -0
  158. package/scripts/hooks/core/session-end-skill-proposals.js +58 -0
  159. package/scripts/hooks/core/session-end.js +25 -0
  160. package/scripts/hooks/core/setup-handler.js +1 -1
  161. package/scripts/hooks/core/task-boundary-reset.js +110 -4
  162. package/scripts/hooks/core/worker-boundary-gate.js +71 -0
  163. package/scripts/hooks/core/worker-tool-first-gate.js +275 -0
  164. package/scripts/hooks/entry/claude-code/post-tool-use.js +2 -2
  165. package/scripts/hooks/entry/claude-code/pre-tool-use.js +7 -2
  166. package/scripts/hooks/entry/claude-code/session-start.js +74 -30
  167. package/scripts/hooks/entry/claude-code/stop.js +47 -1
  168. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +17 -0
  169. package/.workflow/templates/partials/user-commands.hbs +0 -20
@@ -25,7 +25,7 @@ const {
25
25
  isGitRepo,
26
26
  getGitStatus,
27
27
  safeJsonParse,
28
- getTodayDate
28
+ _getTodayDate
29
29
  } = require('./flow-utils')
30
30
  const { color, printSection, success, warn, error } = require('./flow-output');;
31
31
 
@@ -774,6 +774,64 @@ function showContextHealthSummary() {
774
774
  }
775
775
  }
776
776
 
777
+ // B7 (wf-c3b5afab): Surface gate missRate telemetry at session-end so users
778
+ // see rubber-stamping risk without running /wogi-gate-stats. Pulls from the
779
+ // existing getGateStats() API — no duplicated computation or thresholds.
780
+ const MISS_RATE_THRESHOLD = 0.10;
781
+ const MISS_WATCH_WINDOW = '7d';
782
+ const MISS_WATCH_TOP_N = 3;
783
+
784
+ function loadGateStats() {
785
+ let getGateStats;
786
+ try {
787
+ ({ getGateStats } = require('./flow-gate-telemetry'));
788
+ } catch (err) {
789
+ if (process.env.DEBUG) console.error(`[DEBUG] Gate telemetry: ${err.message}`);
790
+ return null;
791
+ }
792
+ try {
793
+ return getGateStats({ since: MISS_WATCH_WINDOW });
794
+ } catch (err) {
795
+ if (process.env.DEBUG) console.error(`[DEBUG] Gate telemetry stats: ${err.message}`);
796
+ return null;
797
+ }
798
+ }
799
+
800
+ function printGateTelemetryWatch(stats = loadGateStats()) {
801
+ console.log('');
802
+ console.log(color('yellow', 'Gate Telemetry — Miss Rate Watch (7d):'));
803
+
804
+ const perGate = stats && stats.perGate ? stats.perGate : null;
805
+ const gates = perGate ? Object.keys(perGate) : [];
806
+ if (!perGate || gates.length === 0) {
807
+ console.log(` ${color('dim', 'No telemetry yet (baseline)')}`);
808
+ return;
809
+ }
810
+
811
+ const ranked = gates
812
+ .map(id => ({ id, ...perGate[id] }))
813
+ .filter(g => g.verdicts && g.verdicts.PASS > 0)
814
+ .sort((a, b) => b.missRate - a.missRate)
815
+ .slice(0, MISS_WATCH_TOP_N);
816
+
817
+ if (ranked.length === 0) {
818
+ console.log(` ${color('dim', 'No PASS events yet — miss rate unmeasurable')}`);
819
+ return;
820
+ }
821
+
822
+ for (const g of ranked) {
823
+ const pct = (g.missRate * 100).toFixed(1);
824
+ const flagged = g.missRate >= MISS_RATE_THRESHOLD;
825
+ const line = ` ${g.id}: miss ${pct}% (${g.missedAfterPass}/${g.verdicts.PASS} PASS events)`;
826
+ if (flagged) {
827
+ console.log(color('red', `${line} ← rubber-stamping risk`));
828
+ } else {
829
+ console.log(line);
830
+ }
831
+ }
832
+ console.log(` ${color('dim', 'Threshold: miss rate ≥ 10% flags a gate as rubber-stamping. See /wogi-gate-stats for full table.')}`);
833
+ }
834
+
777
835
  /**
778
836
  * v1.8.0: Automatic memory management
779
837
  * Part of automatic memory management for teams
@@ -1346,6 +1404,9 @@ async function main() {
1346
1404
  // v1.7.0: Show context health
1347
1405
  showContextHealthSummary();
1348
1406
 
1407
+ // B7 (wf-c3b5afab): Surface gate miss-rate watch — rubber-stamping visibility
1408
+ printGateTelemetryWatch();
1409
+
1349
1410
  // v1.8.0: Automatic memory management
1350
1411
  await automaticMemoryManagement();
1351
1412
 
@@ -1376,7 +1437,15 @@ async function main() {
1376
1437
  showSummary();
1377
1438
  }
1378
1439
 
1379
- main().catch(err => {
1380
- console.error('Error:', err.message);
1381
- process.exit(1);
1382
- });
1440
+ if (require.main === module) {
1441
+ main().catch(err => {
1442
+ console.error('Error:', err.message);
1443
+ process.exit(1);
1444
+ });
1445
+ }
1446
+
1447
+ module.exports = {
1448
+ printGateTelemetryWatch,
1449
+ MISS_RATE_THRESHOLD,
1450
+ MISS_WATCH_TOP_N,
1451
+ };
@@ -94,7 +94,11 @@ function getDefaultState() {
94
94
  count: 0, // Number of bypasses in this session
95
95
  attempts: [], // Array of bypass attempt details
96
96
  autoCreatedTasks: [] // Tasks that were auto-created (bypasses)
97
- }
97
+ },
98
+ // Autonomous walk-away mode (Story C / wf-d712002e)
99
+ // active=true means the AI commits to running the queue without interrupting
100
+ // until it drains, the user sends "stop"/"pause", or the staleness threshold trips.
101
+ autonomousMode: null
98
102
  };
99
103
  }
100
104
 
@@ -915,6 +919,93 @@ if (require.main === module) {
915
919
  }
916
920
  }
917
921
 
922
+ // ============================================================
923
+ // Autonomous Mode (Story C / wf-d712002e)
924
+ // ============================================================
925
+ //
926
+ // Disk is canonical (survives SIGTERM at task-boundary-reset); cache is read-hot.
927
+ // Hot-path hooks (PreToolUse) MUST use the cache; never re-read the disk file.
928
+
929
+ const DEFAULT_AUTONOMOUS_STALENESS_MS = 60 * 60 * 1000;
930
+
931
+ let autonomousModeCache = undefined;
932
+
933
+ function getAutonomousConfig() {
934
+ const cfg = getConfig().autonomousMode || {};
935
+ return {
936
+ stalenessThresholdMs: cfg.stalenessThresholdMs ?? DEFAULT_AUTONOMOUS_STALENESS_MS,
937
+ maxAdversaryInvocations: cfg.maxAdversaryInvocations ?? 30,
938
+ maxQueueSize: cfg.maxQueueSize ?? 100
939
+ };
940
+ }
941
+
942
+ function activateAutonomousMode({ trigger } = {}) {
943
+ const runId = `auto-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
944
+ const record = {
945
+ active: true,
946
+ activatedAt: new Date().toISOString(),
947
+ trigger: trigger || 'unspecified',
948
+ runId,
949
+ adversaryInvocations: { used: 0, breakdown: { autonomousLowConfidence: 0, igrArchitect: 0, manual: 0 } }
950
+ };
951
+ saveSessionState({ autonomousMode: record });
952
+ autonomousModeCache = record;
953
+ return record;
954
+ }
955
+
956
+ function deactivateAutonomousMode() {
957
+ saveSessionState({ autonomousMode: null });
958
+ autonomousModeCache = null;
959
+ }
960
+
961
+ function getAutonomousMode() {
962
+ if (autonomousModeCache !== undefined) return autonomousModeCache;
963
+ const state = loadSessionState();
964
+ autonomousModeCache = state.autonomousMode || null;
965
+ return autonomousModeCache;
966
+ }
967
+
968
+ function isAutonomousActive() {
969
+ const mode = getAutonomousMode();
970
+ return Boolean(mode && mode.active);
971
+ }
972
+
973
+ function isAutonomousStale(mode = getAutonomousMode()) {
974
+ if (!mode || !mode.activatedAt) return false;
975
+ const ageMs = Date.now() - new Date(mode.activatedAt).getTime();
976
+ return ageMs > getAutonomousConfig().stalenessThresholdMs;
977
+ }
978
+
979
+ function rehydrateAutonomousFromDisk() {
980
+ autonomousModeCache = undefined;
981
+ const mode = getAutonomousMode();
982
+ if (mode && mode.active && isAutonomousStale(mode)) {
983
+ deactivateAutonomousMode();
984
+ return { hydrated: false, reason: 'stale', staleMode: mode };
985
+ }
986
+ return { hydrated: Boolean(mode && mode.active), mode };
987
+ }
988
+
989
+ function incrementAdversaryInvocation(source = 'manual') {
990
+ const mode = getAutonomousMode();
991
+ if (!mode || !mode.active) return { allowed: true, used: 0, cap: 0 };
992
+ const cap = getAutonomousConfig().maxAdversaryInvocations;
993
+ const used = (mode.adversaryInvocations?.used ?? 0) + 1;
994
+ const breakdown = { ...(mode.adversaryInvocations?.breakdown || {}) };
995
+ const key = source === 'igr' ? 'igrArchitect'
996
+ : source === 'lowConfidence' ? 'autonomousLowConfidence'
997
+ : 'manual';
998
+ breakdown[key] = (breakdown[key] || 0) + 1;
999
+ const updated = { ...mode, adversaryInvocations: { used, breakdown } };
1000
+ saveSessionState({ autonomousMode: updated });
1001
+ autonomousModeCache = updated;
1002
+ return { allowed: used <= cap, used, cap };
1003
+ }
1004
+
1005
+ function _resetAutonomousCacheForTests() {
1006
+ autonomousModeCache = undefined;
1007
+ }
1008
+
918
1009
  // ============================================================
919
1010
  // Exports
920
1011
  // ============================================================
@@ -979,6 +1070,17 @@ module.exports = {
979
1070
  getBypassSummary,
980
1071
  clearBypassTracking,
981
1072
 
1073
+ // Autonomous mode (Story C / wf-d712002e)
1074
+ activateAutonomousMode,
1075
+ deactivateAutonomousMode,
1076
+ getAutonomousMode,
1077
+ isAutonomousActive,
1078
+ isAutonomousStale,
1079
+ rehydrateAutonomousFromDisk,
1080
+ incrementAdversaryInvocation,
1081
+ getAutonomousConfig,
1082
+ _resetAutonomousCacheForTests,
1083
+
982
1084
  // Path
983
1085
  SESSION_PATH
984
1086
  };
@@ -18,7 +18,7 @@ const path = require('node:path');
18
18
  const {
19
19
  getProjectRoot: _getProjectRoot,
20
20
  getConfig, PATHS,
21
- getTodayDate
21
+ _getTodayDate
22
22
  } = require('./flow-utils')
23
23
  const { color, success, warn, error } = require('./flow-output');;
24
24
 
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow — Skeptical Evaluator (wf-15175dbc / B5).
5
+ *
6
+ * Validating-phase agent that reads the spec + delivery, then forces the AI
7
+ * through a field-by-field enumeration before letting a "done" claim stand.
8
+ *
9
+ * The evaluator composes a prompt with three enumeration passes:
10
+ * 1. UI-field enumeration — for every modified UI surface, list every
11
+ * input/select/textarea/custom field. Reuses the B3 template schema.
12
+ * 2. API-parameter enumeration — for every touched endpoint, list every
13
+ * request/response field.
14
+ * 3. State-key enumeration — for every touched state file or config key,
15
+ * list every entry.
16
+ *
17
+ * For each enumerated item, the evaluator demands a Tier classification
18
+ * (reuse flow-completion-truth-gate EVIDENCE_TIERS) and a confidencePct
19
+ * per the 95/85/75 rubric (wf-f14dcfeb / A4).
20
+ *
21
+ * Composition flow (orchestrator calls this, then invokes Agent tool with the prompt):
22
+ * const { buildSkepticalPrompt, parseSkepticalOutput } = require('./flow-skeptical-evaluator');
23
+ * const prompt = buildSkepticalPrompt({ specMarkdown, diffText, changedFiles, taskId });
24
+ * // ... orchestrator invokes Agent tool with prompt ...
25
+ * const result = parseSkepticalOutput(agentResponse, { taskId });
26
+ *
27
+ * Story: wf-15175dbc (B5)
28
+ * Epic: wf-34290000
29
+ */
30
+
31
+ const path = require('node:path');
32
+
33
+ const { PATHS } = require('./flow-paths');
34
+ const { getConfig } = require('./flow-config-loader');
35
+ const {
36
+ parseBELItems,
37
+ extractSpecStrings,
38
+ verifyBELAgainstDelivery,
39
+ verifySpecBundleCoverage,
40
+ } = require('./flow-completion-truth-gate');
41
+
42
+ const TEMPLATE_PATH = path.join(PATHS.workflow, 'templates', 'tier3-dom-field-inventory.md');
43
+
44
+ function _isDisabled() {
45
+ const cfg = getConfig();
46
+ const igr = cfg.intentGroundedReasoning || {};
47
+ if (igr.enabled === false) return { disabled: true, reason: 'igr-disabled' };
48
+ const se = igr.skepticalEvaluator || {};
49
+ if (se.enabled === false) return { disabled: true, reason: 'skeptical-evaluator-disabled' };
50
+ return { disabled: false };
51
+ }
52
+
53
+ /**
54
+ * Build the system + user prompt for the skeptical evaluator sub-agent.
55
+ *
56
+ * @param {object} opts
57
+ * @param {string} opts.specMarkdown - spec file content
58
+ * @param {string} opts.diffText - git diff
59
+ * @param {string[]} [opts.changedFiles]
60
+ * @param {string} [opts.commitMessage]
61
+ * @param {string} [opts.taskId]
62
+ * @param {string} [opts.bundleText] - built-bundle text if available
63
+ * @returns {{ systemPrompt: string, userPrompt: string, preChecks: object, metadata: object }}
64
+ */
65
+ function buildSkepticalPrompt(opts) {
66
+ if (!opts || typeof opts !== 'object') throw new TypeError('buildSkepticalPrompt: opts required');
67
+ if (typeof opts.specMarkdown !== 'string') throw new TypeError('buildSkepticalPrompt: specMarkdown required');
68
+
69
+ const dis = _isDisabled();
70
+ if (dis.disabled) {
71
+ return { systemPrompt: '', userPrompt: '', preChecks: {}, metadata: { skipped: true, reason: dis.reason, taskId: opts.taskId || null } };
72
+ }
73
+
74
+ const { specMarkdown, diffText = '', changedFiles = [], commitMessage = '', taskId = '', bundleText = '' } = opts;
75
+
76
+ // Run the mechanical pre-checks so the evaluator is grounded in data, not vibes.
77
+ const bel = verifyBELAgainstDelivery({ specMarkdown, diffText, changedFiles, commitMessage });
78
+ const bundle = verifySpecBundleCoverage({ specMarkdown, diffText, changedFiles, bundleText });
79
+ const belItems = parseBELItems(specMarkdown);
80
+ const specStrings = extractSpecStrings(specMarkdown);
81
+
82
+ const systemPrompt = [
83
+ '# Skeptical Evaluator',
84
+ '',
85
+ 'You are the Skeptical Evaluator for WogiFlow\'s validating phase. Your job is to force field-by-field enumeration before a task can be marked "done". Your baseline stance: the claim is unverified until you see every field enumerated and classified.',
86
+ '',
87
+ '## Inputs you receive',
88
+ '',
89
+ '- The task spec (markdown)',
90
+ '- The unified diff',
91
+ '- Changed file paths',
92
+ '- Commit message (if any)',
93
+ '- Mechanical pre-check results from `flow-completion-truth-gate` (BEL grep, spec-bundle grep)',
94
+ '',
95
+ '## Mandatory enumeration passes',
96
+ '',
97
+ '### Pass 1 — UI-field enumeration',
98
+ 'For every modified UI surface (form, filter, wizard, settings panel):',
99
+ '- List every `<input>`, `<select>`, `<textarea>`, or custom input component by its `name` / `data-testid`.',
100
+ '- For each: label, type, default, required, validation, visibility condition.',
101
+ '- Compare to the spec\'s AC. Flag vanished / modified / added fields.',
102
+ '',
103
+ 'If no UI surfaces touched, state explicitly: "UI-field pass: N/A — no UI files modified."',
104
+ 'Reference template: `' + path.relative(process.cwd(), TEMPLATE_PATH) + '`.',
105
+ '',
106
+ '### Pass 2 — API-parameter enumeration',
107
+ 'For every touched API endpoint (request handler, route, or client call):',
108
+ '- List every request parameter (query, path, body field).',
109
+ '- List every response field.',
110
+ '- Compare to the spec\'s AC. Flag additions / removals / type changes.',
111
+ '',
112
+ 'If no API work touched, state explicitly: "API-parameter pass: N/A."',
113
+ '',
114
+ '### Pass 3 — State-key enumeration',
115
+ 'For every touched state file (JSON, YAML, TOML, .env) or config key:',
116
+ '- List every top-level key and each nested key the change introduces / removes.',
117
+ '- Compare to the spec\'s AC.',
118
+ '',
119
+ 'If no state-file work touched, state explicitly: "State-key pass: N/A."',
120
+ '',
121
+ '## Evidence tier + confidence tier on every claim',
122
+ '',
123
+ 'For every enumerated item you classify as preserved/modified/added/vanished, attach:',
124
+ '- `evidenceTier`: 0–4 per `scripts/flow-runtime-verification.js` EVIDENCE_TIERS',
125
+ '- `confidencePct`: exactly 95, 85, or 75 per `.workflow/rubrics/confidence-tiers.md`',
126
+ '- `evidenceNote`: one-line citation (file:line, grep result, or observation)',
127
+ '',
128
+ 'Confidence 75 automatically flags the claim `UNVERIFIED`. Do not upgrade without evidence.',
129
+ '',
130
+ '## Output contract',
131
+ '',
132
+ 'Return ONE JSON object with shape:',
133
+ '```json',
134
+ '{',
135
+ ' "taskId": "<id>",',
136
+ ' "uiFieldPass": { "ran": true|false, "reason": "...", "findings": [...] },',
137
+ ' "apiParameterPass": { ... },',
138
+ ' "stateKeyPass": { ... },',
139
+ ' "overallVerdict": "PASS" | "CONCERN" | "FAIL",',
140
+ ' "blockers": ["one string per blocking issue"],',
141
+ ' "unverifiedClaims": ["one string per claim at confidence 75"]',
142
+ '}',
143
+ '```',
144
+ '',
145
+ 'No prose. No markdown fences around the JSON. Just the object.',
146
+ ].join('\n');
147
+
148
+ const userPrompt = [
149
+ '# Inputs',
150
+ '',
151
+ `- Task ID: ${taskId || '<unknown>'}`,
152
+ `- Changed files (${changedFiles.length}): ${changedFiles.slice(0, 20).join(', ')}${changedFiles.length > 20 ? ', ...' : ''}`,
153
+ '',
154
+ '## Spec',
155
+ '```markdown',
156
+ _truncate(specMarkdown, 12000),
157
+ '```',
158
+ '',
159
+ '## Unified diff',
160
+ '```',
161
+ _truncate(diffText, 12000),
162
+ '```',
163
+ '',
164
+ '## Commit message',
165
+ '```',
166
+ _truncate(commitMessage, 2000),
167
+ '```',
168
+ '',
169
+ '## Mechanical pre-checks (from flow-completion-truth-gate)',
170
+ '',
171
+ '### BEL grep',
172
+ `- items parsed: ${belItems.length}`,
173
+ `- ok: ${bel.ok}`,
174
+ `- uncovered: ${bel.uncoveredItems.length}`,
175
+ bel.uncoveredItems.length ? `- uncovered samples: ${bel.uncoveredItems.slice(0, 5).map((u) => u.text).join(' | ')}` : '',
176
+ '',
177
+ '### Spec-bundle coverage',
178
+ `- ok: ${bundle.ok}`,
179
+ ..._bundleSummaryLines(bundle),
180
+ '',
181
+ '## Extracted spec strings (reference)',
182
+ `- backtickIds (${specStrings.backtickIds.length}): ${specStrings.backtickIds.slice(0, 8).join(', ')}`,
183
+ `- filePaths (${specStrings.filePaths.length}): ${specStrings.filePaths.slice(0, 8).join(', ')}`,
184
+ `- constants (${specStrings.constants.length}): ${specStrings.constants.slice(0, 8).join(', ')}`,
185
+ `- routes (${specStrings.routes.length}): ${specStrings.routes.slice(0, 8).join(', ')}`,
186
+ '',
187
+ '## Your task',
188
+ '',
189
+ 'Run the three enumeration passes described in the system prompt. Return the JSON object. Be skeptical — force a verdict on every field.',
190
+ ].filter(Boolean).join('\n');
191
+
192
+ return {
193
+ systemPrompt,
194
+ userPrompt,
195
+ preChecks: { bel, bundle, belItems, specStrings },
196
+ metadata: { taskId: taskId || null, changedFileCount: changedFiles.length },
197
+ };
198
+ }
199
+
200
+ function _truncate(text, cap) {
201
+ const s = String(text || '');
202
+ return s.length > cap ? s.slice(0, cap) + `\n\n[... truncated at ${cap} chars]` : s;
203
+ }
204
+
205
+ function _bundleSummaryLines(bundle) {
206
+ const out = [];
207
+ for (const [cat, v] of Object.entries(bundle.coverage || {})) {
208
+ if (v.total === 0) continue;
209
+ out.push(` - ${cat}: ${v.hit}/${v.total} (need ${v.threshold.toFixed(2)})`);
210
+ if (v.missing && v.missing.length > 0) out.push(` missing: ${v.missing.slice(0, 4).join(', ')}`);
211
+ }
212
+ return out;
213
+ }
214
+
215
+ /**
216
+ * Parse the sub-agent's JSON response.
217
+ * @param {string} response
218
+ * @param {object} [ctx]
219
+ * @returns {object}
220
+ */
221
+ function parseSkepticalOutput(response, ctx = {}) {
222
+ if (typeof response !== 'string' || response.trim().length === 0) {
223
+ return { ok: false, reason: 'empty response', overallVerdict: 'FAIL' };
224
+ }
225
+ let parsed;
226
+ try {
227
+ // Try raw
228
+ parsed = JSON.parse(response);
229
+ } catch (_err) {
230
+ // Try extracting JSON object
231
+ const m = response.match(/\{[\s\S]*\}/);
232
+ if (!m) return { ok: false, reason: 'no JSON object found', overallVerdict: 'FAIL' };
233
+ try {
234
+ parsed = JSON.parse(m[0]);
235
+ } catch (err) {
236
+ return { ok: false, reason: `JSON parse failed: ${err.message}`, overallVerdict: 'FAIL' };
237
+ }
238
+ }
239
+ if (!parsed || typeof parsed !== 'object') {
240
+ return { ok: false, reason: 'response is not an object', overallVerdict: 'FAIL' };
241
+ }
242
+ const verdict = parsed.overallVerdict || 'FAIL';
243
+ return {
244
+ ok: verdict === 'PASS',
245
+ overallVerdict: verdict,
246
+ uiFieldPass: parsed.uiFieldPass || { ran: false },
247
+ apiParameterPass: parsed.apiParameterPass || { ran: false },
248
+ stateKeyPass: parsed.stateKeyPass || { ran: false },
249
+ blockers: Array.isArray(parsed.blockers) ? parsed.blockers : [],
250
+ unverifiedClaims: Array.isArray(parsed.unverifiedClaims) ? parsed.unverifiedClaims : [],
251
+ taskId: parsed.taskId || ctx.taskId || null,
252
+ };
253
+ }
254
+
255
+ module.exports = {
256
+ buildSkepticalPrompt,
257
+ parseSkepticalOutput,
258
+ TEMPLATE_PATH,
259
+ };
260
+
261
+ if (require.main === module) {
262
+ const cmd = process.argv[2];
263
+ if (cmd === 'prompt') {
264
+ const specFile = process.argv[3];
265
+ if (!specFile) { console.error('usage: flow-skeptical-evaluator prompt <spec.md>'); process.exit(2); }
266
+ const fs = require('node:fs');
267
+ const specMarkdown = fs.readFileSync(specFile, 'utf8');
268
+ const built = buildSkepticalPrompt({ specMarkdown, diffText: '', changedFiles: [], taskId: 'cli' });
269
+ console.log('--- SYSTEM ---\n' + built.systemPrompt + '\n--- USER ---\n' + built.userPrompt);
270
+ } else {
271
+ console.error('usage: flow-skeptical-evaluator prompt <spec.md>');
272
+ process.exit(2);
273
+ }
274
+ }
@@ -229,7 +229,7 @@ async function fetchDocsViaContext7(technology) {
229
229
  // SKILL FILE GENERATION
230
230
  // ============================================
231
231
 
232
- function generateSkillMd(tech, docs) {
232
+ function _generateSkillMd(tech, docs) {
233
233
  const date = getTodayDate();
234
234
 
235
235
  return `---
@@ -595,7 +595,7 @@ This skill is loaded on-demand when:
595
595
  * v2.0 format includes: type, ecosystem, loadWith, tokenCost
596
596
  */
597
597
  function generateSkillsIndex(technologies, selections) {
598
- const { getSkillType, getParentFramework, ECOSYSTEMS } = getTechOptions();
598
+ const { getSkillType, getParentFramework, _ECOSYSTEMS } = getTechOptions();
599
599
  const skills = {};
600
600
 
601
601
  // First pass: identify all technologies and their types
@@ -759,7 +759,7 @@ function migrateOldSkills(projectRoot) {
759
759
  * @param {Object} skillContext - Additional context { type, parentFramework, ecosystemSkills }
760
760
  */
761
761
  async function writeSkillFiles(tech, docs, projectRoot, skillContext = {}) {
762
- const { getSkillType, getParentFramework } = getTechOptions();
762
+ const { getSkillType, _getParentFramework } = getTechOptions();
763
763
 
764
764
  const skillId = tech.value.toLowerCase().replace(/[^a-z0-9]/g, '-');
765
765
  const skillDir = path.join(projectRoot, '.claude', 'skills', skillId);
@@ -29,9 +29,6 @@ function log(color, ...args) {
29
29
  console.log(colors[color] + args.join(' ') + colors.reset);
30
30
  }
31
31
 
32
- // Alias getConfig as loadConfig for minimal code changes
33
- const loadConfig = getConfig;
34
-
35
32
  function isLearningEnabled(config, trigger) {
36
33
  if (!config?.skillLearning?.enabled) return false;
37
34
  if (!config?.skillLearning?.autoExtract) return false;
@@ -81,7 +78,7 @@ function getChangedFiles(staged = false) {
81
78
  }
82
79
  }
83
80
 
84
- function getRecentCommitFiles(count = 1) {
81
+ function _getRecentCommitFiles(count = 1) {
85
82
  try {
86
83
  const cmd = `git diff HEAD~${count} --name-only`;
87
84
  const output = execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
@@ -387,7 +384,7 @@ function formatSemanticChanges(semanticChanges) {
387
384
 
388
385
  const lines = [];
389
386
 
390
- for (const { file, changes } of semanticChanges) {
387
+ for (const { _file, changes } of semanticChanges) {
391
388
  if (changes.length === 0) continue;
392
389
 
393
390
  for (const change of changes) {
@@ -714,7 +711,7 @@ async function main() {
714
711
  process.exit(0);
715
712
  }
716
713
 
717
- const config = loadConfig();
714
+ const config = getConfig();
718
715
 
719
716
  if (!isLearningEnabled(config, options.trigger)) {
720
717
  if (options.verbose) {