wogiflow 2.29.6 → 2.29.8
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/docs/claude-code-compatibility.md +19 -1
- package/.claude/settings.json +1 -1
- package/.workflow/templates/claude-md.hbs +18 -34
- package/.workflow/templates/partials/methodology-rules.hbs +82 -165
- package/lib/workspace-channel-server.js +2 -2
- package/lib/workspace-task-injector.js +11 -5
- package/package.json +5 -2
- package/scripts/flow-audit-gates.js +61 -2
- package/scripts/flow-config-defaults.js +18 -0
- package/scripts/flow-defer-auth.js +0 -1
- package/scripts/flow-export-scanner.js +9 -9
- package/scripts/flow-figma-match.js +4 -2
- package/scripts/flow-hypothesis-generator.js +5 -4
- package/scripts/flow-model-router.js +2 -2
- package/scripts/flow-prompt-template.js +4 -5
- package/scripts/flow-repo-map.js +8 -4
- package/scripts/flow-source-fidelity.js +0 -1
- package/scripts/flow-standards-checker.js +117 -11
- package/scripts/hooks/core/deferral-gate.js +1 -1
- package/scripts/hooks/core/long-input-enforcement.js +5 -4
|
@@ -77,6 +77,24 @@ flow parallel check # See available parallel tasks
|
|
|
77
77
|
| 2.18.0+ | 2.1.108+ | ENABLE_PROMPT_CACHING_1H guidance, /recap awareness, /doctor MCP duplicate-scope mirror in `/wogi-health` |
|
|
78
78
|
| 2.27.0+ | 2.1.116+ | Sandbox dangerous-path safety on auto-allow, agent frontmatter hooks for `--agent`, `/resume` large-session speedup, MCP stdio concurrent startup |
|
|
79
79
|
| 2.27.0+ | 2.1.117+ | Native bfs/ugrep via Bash (hook audit documented), Opus 4.7 /context fix (estimator already percentage-based), Pro/Max effort default shift (advisory delta documented), agent frontmatter `mcpServers` for `--agent`, subagent model-mismatch malware-warning fix, managed-settings plugin marketplace enforcement |
|
|
80
|
+
| 2.29.6+ | 2.1.132+ | Statusline `context_window` token-count accuracy fix (release notes: was reporting cumulative session totals — may have affected `wogi-statusline-setup` percentage presets if percentage was derived from cumulative tokens), Bedrock/Vertex `ENABLE_PROMPT_CACHING_1H` 400-error fix (recommendation now safe on those providers), `CLAUDE_CODE_SESSION_ID` available in Bash subprocess env |
|
|
81
|
+
| 2.29.7+ | 2.1.133+ | **Subagent skill discovery fix** (CRITICAL for IGR — Architect/Adversary/Skeptical-Evaluator are subagents; if they were missing skills, IGR was silently impaired across versions 2.1.128–2.1.132); **`worktree.baseRef` setting (fresh\|head) reverted to `origin/<default>` default** (was local HEAD since 2.1.128) — wogi-flow's `scripts/flow-worktree.js` users with unpushed local commits should set `worktree.baseRef: "head"` in `.claude/settings.json` to preserve prior behavior; **hooks now receive `effort.level` JSON field + `$CLAUDE_EFFORT` env var** (opportunity for wogi-flow gates to adjust thresholds based on effort — not yet wired); `/effort` no longer leaks across concurrent sessions (workspace-mode benefit); Edit/Write allow rules at drive-root fixed; `sandbox.bwrapPath`/`sandbox.socatPath` managed settings (Linux/WSL); `parentSettingsBehavior` admin-tier key |
|
|
82
|
+
| 2.29.7+ | 2.1.136+ | **`AskUserQuestion` multi-select array discard fixed** (wogi-flow uses AskUserQuestion extensively across skills — automatic fix); **`settings.autoMode.hard_deny`** new unconditional-block mechanism (potential future wogi-flow use for non-bypassable gates like routing/deferral); extended-thinking redacted-block API 400 fixed (wogi-flow's IGR Architect on Opus benefits); MCP servers no longer silently disappear after `/clear`; OAuth refresh-token races fixed (better stability for multi-MCP users); `--resume`/`--continue` no longer fails on underscored project paths; plan mode now correctly blocks file writes when matching Edit allow rule exists; subagent file pickers find files in dirs with >100 entries; many TUI cosmetic fixes (CJK rendering, autocomplete, color artifacts) |
|
|
83
|
+
|
|
84
|
+
### Worktree-baseRef recommendation (2.1.133+)
|
|
85
|
+
|
|
86
|
+
If you use wogi-flow's worktree feature for parallel task execution AND have unpushed local commits you want available in new worktrees:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
// .claude/settings.json
|
|
90
|
+
{
|
|
91
|
+
"worktree": {
|
|
92
|
+
"baseRef": "head"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Without this, new worktrees branch from `origin/<default>` (the 2.1.133 default) and your unpushed commits won't be available in them. wogi-flow's `scripts/flow-worktree.js` doesn't currently configure this — it inherits whatever Claude Code's default is.
|
|
80
98
|
|
|
81
99
|
### Environment Variables (2.1.19+)
|
|
82
100
|
|
|
@@ -368,7 +386,7 @@ await cancelTask('wf-123', 'superseded', false);
|
|
|
368
386
|
|
|
369
387
|
### Features in 2.1.108+
|
|
370
388
|
|
|
371
|
-
- **`ENABLE_PROMPT_CACHING_1H` env var (RECOMMENDED for non-subscribers)**: Opts into **1-hour prompt-cache TTL** on **API key, Bedrock, Vertex, and Foundry** providers. Subscribers (Claude Pro, Max, Team, Enterprise via claude.ai OAuth) already get 1h TTL by default — this flag is a **no-op for them**. The complementary `FORCE_PROMPT_CACHING_5M` pins to 5min, and the older `ENABLE_PROMPT_CACHING_1H_BEDROCK` is deprecated but still honored. **Impact on WogiFlow (HIGH)**: WogiFlow sessions load a large, stable prefix every turn — CLAUDE.md (~300 lines), state files (`ready.json`, `decisions.md`, `app-map.md`), phase files, and pinned spec context. At the default 5min TTL, any pause longer than 5 minutes (user thinking, a long `flow` CLI run, a meeting mid-session) invalidates the cache and the next turn pays the full input-token cost again. At 1h TTL, the same prefix stays cached across those pauses, yielding **substantial token-cost reduction** on typical multi-hour WogiFlow work. **Action for API-key / Bedrock / Vertex / Foundry users**: `export ENABLE_PROMPT_CACHING_1H=1` in your shell profile. **Action for subscribers**: none (already enabled). **Risk**: none — if set on a subscriber account it is ignored; if set when not supported, it silently falls back.
|
|
389
|
+
- **`ENABLE_PROMPT_CACHING_1H` env var (RECOMMENDED for non-subscribers)**: Opts into **1-hour prompt-cache TTL** on **API key, Bedrock, Vertex, and Foundry** providers. Subscribers (Claude Pro, Max, Team, Enterprise via claude.ai OAuth) already get 1h TTL by default — this flag is a **no-op for them**. The complementary `FORCE_PROMPT_CACHING_5M` pins to 5min, and the older `ENABLE_PROMPT_CACHING_1H_BEDROCK` is deprecated but still honored. **Impact on WogiFlow (HIGH)**: WogiFlow sessions load a large, stable prefix every turn — CLAUDE.md (~300 lines), state files (`ready.json`, `decisions.md`, `app-map.md`), phase files, and pinned spec context. At the default 5min TTL, any pause longer than 5 minutes (user thinking, a long `flow` CLI run, a meeting mid-session) invalidates the cache and the next turn pays the full input-token cost again. At 1h TTL, the same prefix stays cached across those pauses, yielding **substantial token-cost reduction** on typical multi-hour WogiFlow work. **Action for API-key / Bedrock / Vertex / Foundry users**: `export ENABLE_PROMPT_CACHING_1H=1` in your shell profile. **Action for subscribers**: none (already enabled). **Risk**: none — if set on a subscriber account it is ignored; if set when not supported, it silently falls back. **Bedrock/Vertex caveat**: Some Claude Code versions before 2.1.132 returned 400 errors when this flag was set on Bedrock/Vertex (per the 2.1.132 release notes). Fixed in **2.1.132+** — Bedrock/Vertex users on older Claude Code should upgrade before setting the flag.
|
|
372
390
|
|
|
373
391
|
- **`/recap` command and session recap feature**: Provides context when returning to a session. Configurable in `/config` and manually invocable with `/recap`. For users with telemetry disabled (Bedrock/Vertex/Foundry/`DISABLE_TELEMETRY`), recap is still enabled by default; opt out via `/config` or `CLAUDE_CODE_ENABLE_AWAY_SUMMARY=0`. **Overlap with WogiFlow**: `/wogi-morning`, `/wogi-session-end`, and `/wogi-pre-compact` already provide durable recap via state files. `/recap` is ephemeral (summarizes the current session); WogiFlow's state survives session exit. Use both: `/recap` for intra-session context, `/wogi-morning` for cross-session pickup.
|
|
374
392
|
|
package/.claude/settings.json
CHANGED
|
@@ -170,6 +170,6 @@
|
|
|
170
170
|
},
|
|
171
171
|
"_comment_dynamicHooks": "TaskCreated (2.1.84+) and PermissionDenied (2.1.88+) are added by postinstall.js when the CC version supports them. They must NOT be committed statically — CC rejects the entire settings file if it encounters an unknown hook event name.",
|
|
172
172
|
"_wogiFlowManaged": true,
|
|
173
|
-
"_wogiFlowVersion": "2.
|
|
173
|
+
"_wogiFlowVersion": "2.29.7",
|
|
174
174
|
"_comment": "Shared WogiFlow hook configuration. Committed to repo for team use. User-specific overrides go in settings.local.json."
|
|
175
175
|
}
|
|
@@ -148,53 +148,37 @@ When in doubt, route through `/wogi-start` which will classify correctly.
|
|
|
148
148
|
|
|
149
149
|
### Anti-Deferral Rule (MANDATORY — ZERO TOLERANCE)
|
|
150
150
|
|
|
151
|
-
**You MUST NEVER autonomously defer, skip, deprioritize, or drop items from the user's input.**
|
|
151
|
+
**You MUST NEVER autonomously defer, skip, deprioritize, or drop items from the user's input.** If the user provides N items, ALL N become tracked work items. No judgment calls about "important" vs. "enhancement" vs. "long-term."
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
**Deferral-specific traps** (in addition to master Anti-Rationalization Checklist above):
|
|
154
|
+
- "Items 6-9 are enhancements, I'll focus on fixes first" → WRONG. Create tasks for ALL items.
|
|
155
|
+
- "I already created the important ones" → WRONG. Important is not your call.
|
|
156
|
+
- "I'll defer these as lower priority" → WRONG. Suggest priority; every item still gets a task.
|
|
157
|
+
- "The ready queue would be too large" → WRONG. A large queue is correct; a filtered queue is data loss.
|
|
158
|
+
- "This one was labeled 'long-term'" → WRONG. The user decides when to execute, not you.
|
|
154
159
|
|
|
155
|
-
**
|
|
156
|
-
- "Items 6-9 are enhancements, I'll focus on the fixes first" → WRONG. Create tasks for ALL items.
|
|
157
|
-
- "This one was labeled 'long-term' by the team" → WRONG. Track it. The user decides when to execute, not you.
|
|
158
|
-
- "I'll defer these as lower priority" → WRONG. You may SUGGEST a priority order, but every item must be a tracked task.
|
|
159
|
-
- "The ready queue would be too large" → WRONG. A large queue is correct. A filtered queue is data loss.
|
|
160
|
-
- "I already created the important ones" → WRONG. Important is not your call. Create ALL of them.
|
|
160
|
+
**MAY**: suggest priority order (P0/P1/P2/P3); group related items into stories (every item appears as a criterion in ≥1 story); ask the user to confirm scope.
|
|
161
161
|
|
|
162
|
-
**
|
|
163
|
-
- Suggest a priority order (P0/P1/P2/P3) — but ALL items get tasks regardless of priority
|
|
164
|
-
- Group related items into stories — but every item must appear as a criterion in at least one story
|
|
165
|
-
- Ask the user to confirm scope — but do NOT preemptively filter
|
|
162
|
+
**MUST NEVER**: silently drop items based on AI judgment; create tasks for a subset without explicit user approval to defer the rest; use words like "deferred"/"skipped"/"not created" for user-provided items.
|
|
166
163
|
|
|
167
|
-
|
|
168
|
-
- Silently drop items because you judged them as "enhancements" or "nice-to-haves"
|
|
169
|
-
- Create tasks for only a subset of items without explicit user approval to defer the rest
|
|
170
|
-
- Use words like "deferred", "skipped", or "not created" for items the user provided
|
|
164
|
+
Applies to `/wogi-start`, `/wogi-story`, `/wogi-epics`, `/wogi-extract-review`, and any command converting user input into tracked work.
|
|
171
165
|
|
|
172
|
-
|
|
166
|
+
### Mid-Execution Anti-Deferral (AFTER TASKS ARE CREATED)
|
|
173
167
|
|
|
174
|
-
|
|
168
|
+
**Reordering is permitted. Deferring is not.** Once work is tracked, you MUST NOT propose to skip, postpone, drop, or "deprioritize to later" — regardless of risk, cost, or token-weight. Only sequence changes. "Revisit later" and "deprioritize" are soft-defer euphemisms.
|
|
175
169
|
|
|
176
|
-
|
|
170
|
+
When genuinely unsure work is still needed: ask explicitly — "Do you still want wf-XXXX to ship this epic, or drop it?" User decides.
|
|
177
171
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
**MAY do after tasks are tracked**: propose sequence/parallelization/prerequisites; flag risks without using them to drop scope.
|
|
181
|
-
|
|
182
|
-
**MUST NEVER do**: propose to "defer", skip based on AI judgment, present a plan that silently omits tracked work.
|
|
183
|
-
|
|
184
|
-
**When genuinely unsure work is still needed**: ask explicitly — "Do you still want wf-XXXX to ship this epic, or drop it?" User decides, not you.
|
|
185
|
-
|
|
186
|
-
### Review-Findings Anti-Deferral (MANDATORY — INCIDENT-DRIVEN)
|
|
172
|
+
### Review-Findings Anti-Deferral
|
|
187
173
|
|
|
188
174
|
Extends Mid-Execution Anti-Deferral to `/wogi-review`, `/wogi-audit`, `/wogi-triage` findings. When the user says "fix all findings" / "option 1" / any variant meaning "address everything":
|
|
189
175
|
|
|
190
176
|
1. Ship a fix for every finding at evidence tier ≥ 1, regardless of effort estimate.
|
|
191
|
-
2. Never silently convert a finding to "deferred" in commit/release notes without the user
|
|
192
|
-
3. If
|
|
193
|
-
4. Never list a finding in release notes without actually fixing it.
|
|
194
|
-
|
|
195
|
-
Transparency ≠ permission. "Low-risk can wait" and "restructure warrants separate release" are AI judgment calls — they're the user's to make. Apply the master Anti-Rationalization Checklist above.
|
|
177
|
+
2. Never silently convert a finding to "deferred" in commit/release notes without the user saying "defer X."
|
|
178
|
+
3. If too large for the current release → STOP and ask: "Finding X requires ~Y min. Ship / split / defer?"
|
|
179
|
+
4. Never list a finding in release notes without actually fixing it.
|
|
196
180
|
|
|
197
|
-
|
|
181
|
+
"Low-risk can wait" and "restructure warrants separate release" are AI judgment calls — the user's to make.
|
|
198
182
|
|
|
199
183
|
### Task ID Format (MANDATORY)
|
|
200
184
|
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
## WogiFlow Methodology Rules
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Rules below are enforced by shipped hooks; the prose is so Claude understands the contract. Apply the master Anti-Rationalization Checklist (top of CLAUDE.md) to any rule that doesn't list its own.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
### Research Before Propose
|
|
8
8
|
|
|
9
|
-
Before proposing a fix, plan, or spec, read 2+ files from `.workflow/state/`, `.workflow/changes/`, `.workflow/specs/`, or `.workflow/epics
|
|
9
|
+
Before proposing a fix, plan, or spec, read 2+ files from `.workflow/state/`, `.workflow/changes/`, `.workflow/specs/`, or `.workflow/epics/`. Clarifying questions are a valid escape; proposing without evidence is not.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Enforced by: `research-evidence-gate.js` (blocks `→ spec_review` / `→ coding` transitions and spec-file writes until threshold met; cleared at task start, session-end, and post-compact). Config: `hooks.rules.researchEvidenceGate.{enabled,minEvidence}` (defaults `true`, `2`).
|
|
11
|
+
Enforced by: `research-evidence-gate.js` (blocks `→ spec_review` / `→ coding` and spec-file writes until threshold met). Config: `hooks.rules.researchEvidenceGate.{enabled,minEvidence}` (defaults `true`, `2`).
|
|
14
12
|
|
|
15
13
|
---
|
|
16
14
|
|
|
17
15
|
### Completion-Claim Honesty Scan
|
|
18
16
|
|
|
19
|
-
At session-end and `flow health`, `ready.json` entries are scanned (surfaced, not blocked) for
|
|
20
|
-
- **Status-mismatch** — free-text says "done/completed/shipped" while `status` is partial/blocked/failed.
|
|
21
|
-
- **Negation-vs-evidence** — free-text says "no outages / 0 regressions" while `hotfixes[]` / `incidents[]` / `regressions[]` is non-empty.
|
|
17
|
+
At session-end and `flow health`, `ready.json` entries are scanned (surfaced, not blocked) for status-mismatch (free-text says "done" while `status` is partial/blocked) and negation-vs-evidence (free-text says "no outages" while `hotfixes[]`/`incidents[]`/`regressions[]` is non-empty).
|
|
22
18
|
|
|
23
19
|
Enforced by: `flow-completion-truth-gate.js → scanForClaimContradictions()`.
|
|
24
20
|
|
|
@@ -26,7 +22,7 @@ Enforced by: `flow-completion-truth-gate.js → scanForClaimContradictions()`.
|
|
|
26
22
|
|
|
27
23
|
### Merge-Plan Artifact Gate
|
|
28
24
|
|
|
29
|
-
`/wogi-finalize` requires `.workflow/scratch/merge-plan.md` for merges >5 commits or any cross-repo merge. Every commit in `git log <base>..<branch>` must map to `port | adapt | skip-style | superseded | skip-with-reason`; SHA-line count
|
|
25
|
+
`/wogi-finalize` requires `.workflow/scratch/merge-plan.md` for merges >5 commits or any cross-repo merge. Every commit in `git log <base>..<branch>` must map to `port | adapt | skip-style | superseded | skip-with-reason`; SHA-line count = commit count. ≥20% restructure-pattern files biases affected commits toward `adapt`.
|
|
30
26
|
|
|
31
27
|
Enforced by: `flow-structure-sensor.js`, `.claude/commands/wogi-finalize.md` Step 2.5.
|
|
32
28
|
|
|
@@ -34,58 +30,46 @@ Enforced by: `flow-structure-sensor.js`, `.claude/commands/wogi-finalize.md` Ste
|
|
|
34
30
|
|
|
35
31
|
### Story Creation Quality Gates
|
|
36
32
|
|
|
37
|
-
`/wogi-story` runs 5 P0 spec-quality gates at creation time
|
|
33
|
+
`/wogi-story` runs 5 P0 spec-quality gates at creation time:
|
|
38
34
|
|
|
39
|
-
1. **Long Input** — ≥40 lines or ≥5
|
|
40
|
-
2. **Item Reconciliation** — ≥3 items → enumerated Item Manifest; unmapped items
|
|
41
|
-
3. **Consumer Impact Analysis** — refactoring keywords trigger `git grep
|
|
42
|
-
4. **Scope-Confidence Audit** — assumption patterns
|
|
43
|
-
5. **Intent Bootstrap Coordination** — schedules IGR
|
|
35
|
+
1. **Long Input** — ≥40 lines or ≥5 items → route to `/wogi-extract-review`.
|
|
36
|
+
2. **Item Reconciliation** — ≥3 items → enumerated Item Manifest; unmapped items warn.
|
|
37
|
+
3. **Consumer Impact Analysis** — refactoring keywords trigger `git grep`; ≥5 breaking → phased migration.
|
|
38
|
+
4. **Scope-Confidence Audit** — assumption patterns verified against codebase; findings → Pending Clarifications.
|
|
39
|
+
5. **Intent Bootstrap Coordination** — schedules IGR bootstrap once.
|
|
44
40
|
|
|
45
|
-
All
|
|
41
|
+
All fail-open. Bypass for tests via `--skip-gates`. Config: `storyFlow.*`.
|
|
46
42
|
|
|
47
43
|
---
|
|
48
44
|
|
|
49
45
|
### Workspace Worker Contract
|
|
50
46
|
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
**Tool-First Turn**: Every turn after `UserPromptSubmit` must contain ≥1 tool call. In strict mode (default), the first assistant content block must be `tool_use`, not text. Pure-text responses are invisible to the user (they only see the manager terminal) and disqualify the worker from the three-state contract below.
|
|
54
|
-
|
|
55
|
-
**Three-State End-of-Turn**: Exactly one of:
|
|
56
|
-
1. **ACTION** — start next pre-approved channel dispatch via `/wogi-start <nextId>`.
|
|
57
|
-
2. **ESCALATION** — channel-dispatch `## QUESTION: ...` to the manager.
|
|
58
|
-
3. **IDLE** — zero pending dispatches AND zero in-progress tasks.
|
|
59
|
-
|
|
60
|
-
Hedging phrases ("awaiting your signal", "let me know", "standing by", "should I continue") are mechanically forbidden — visibility is NOT a substitute for action; the manager already pre-approved the dispatch by queuing it.
|
|
47
|
+
*Workspace worker mode only (`WOGI_WORKSPACE_ROOT` set + `WOGI_REPO_NAME !== 'manager'`). Skip in solo sessions.*
|
|
61
48
|
|
|
62
|
-
**
|
|
49
|
+
- **Tool-First Turn**: every turn after `UserPromptSubmit` must contain ≥1 tool call. In strict mode (default), the first content block must be `tool_use`. Pure-text responses are invisible to the user.
|
|
50
|
+
- **Three-State End-of-Turn**: exactly one of ACTION (`/wogi-start <nextId>`), ESCALATION (channel-dispatch `## QUESTION:`), or IDLE.
|
|
51
|
+
- **Hedging forbidden**: "awaiting your signal", "let me know", "standing by", "should I continue".
|
|
52
|
+
- **No direct user prompts**: `AskUserQuestion` is blocked; questions go through channel dispatch.
|
|
63
53
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
Enforced by: `worker-tool-first-gate.js` (G1/G4/Gap B), `worker-boundary-gate.js`, `flow-worker-question-classifier.js`. Config: `workspace.toolFirstTurnGate.{enabled,strict}`, `workspace.blockAskUserQuestionInWorker`, `workspace.aiWorkerQuestionClassifier.*`, `workspace.autoPickupChannelDispatches`.
|
|
54
|
+
Enforced by: `worker-tool-first-gate.js` (G1/G4/Gap B), `worker-boundary-gate.js`, `flow-worker-question-classifier.js`. Config: `workspace.toolFirstTurnGate.{enabled,strict}`, `workspace.blockAskUserQuestionInWorker`, `workspace.aiWorkerQuestionClassifier.*`. Long-form: `.claude/rules/_internal/worker-tool-first-turn.md`.
|
|
67
55
|
|
|
68
56
|
---
|
|
69
57
|
|
|
70
58
|
### Workspace Manager Silent-Halt Detection
|
|
71
59
|
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
Every manager→worker dispatch is tracked. A pending dispatch past its `expectedDeadline` with no `task-complete` or `worker-stopped` message = silent death, surfaced on the manager's next turn via `UserPromptSubmit` `additionalContext`. Default `expectedDurationMs = 30min`; callers override per-dispatch for long tasks.
|
|
60
|
+
*Workspace manager mode only.*
|
|
75
61
|
|
|
76
|
-
Three terminal states:
|
|
62
|
+
Every manager→worker dispatch is tracked. A pending dispatch past `expectedDeadline` with no `task-complete`/`worker-stopped` = silent halt, surfaced on next turn via `UserPromptSubmit` `additionalContext`. Default `expectedDurationMs = 30min`. Three terminal states: Completed / Graceful-stop / Silent-halt.
|
|
77
63
|
|
|
78
|
-
Enforced by: `lib/workspace-dispatch-tracking.js`, `.workspace/state/dispatched-tasks.json` (ring buffer, last 100
|
|
64
|
+
Enforced by: `lib/workspace-dispatch-tracking.js`, `.workspace/state/dispatched-tasks.json` (ring buffer, last 100).
|
|
79
65
|
|
|
80
66
|
---
|
|
81
67
|
|
|
82
68
|
### Main-Mode Question Classifier
|
|
83
69
|
|
|
84
|
-
*
|
|
70
|
+
*Solo sessions with `taskBoundaryReset.enabled: true`.*
|
|
85
71
|
|
|
86
|
-
Before
|
|
87
|
-
|
|
88
|
-
**Prefer explicit `flow ask "<question>"`** — it writes the marker directly and runs before the classifier (short-circuits with `pending-question-deferred`). The classifier is the safety net for when you forget.
|
|
72
|
+
Before Stop hook fires SIGTERM, a Haiku classifier inspects the final assistant message. Open user-facing question + no `pending-question.json` → write marker, defer restart. Prefer explicit `flow ask "<question>"` (writes marker directly, short-circuits the classifier). Fail-open throughout.
|
|
89
73
|
|
|
90
74
|
Enforced by: `task-boundary-reset.js → consumeAndTriggerRestart()`. Config: `mainModeQuestionClassifier.{enabled,minConfidence,model}`.
|
|
91
75
|
|
|
@@ -93,197 +77,130 @@ Enforced by: `task-boundary-reset.js → consumeAndTriggerRestart()`. Config: `m
|
|
|
93
77
|
|
|
94
78
|
### Main-Mode Auto-Pickup After Clean Restart
|
|
95
79
|
|
|
96
|
-
*
|
|
97
|
-
|
|
98
|
-
After a task-boundary restart triggered by a **clean** completion (not error/blocked/killed), the next SessionStart context injects `AUTO-PICKUP MODE ACTIVE` with the next ready task ID. The first user message → invoke `Skill(skill="wogi-start", args="<nextReadyId>")` immediately, regardless of message content. No "what's next?", no summary, no proposing alternatives.
|
|
80
|
+
*Solo sessions with `taskBoundaryReset.enabled: true` AND `autoPickupNextTask: true` (default).*
|
|
99
81
|
|
|
100
|
-
|
|
82
|
+
After a clean-completion task-boundary restart, SessionStart context injects `AUTO-PICKUP MODE ACTIVE` with the next ready task ID. First user message → invoke `Skill(skill="wogi-start", args="<nextReadyId>")` immediately, regardless of message content.
|
|
101
83
|
|
|
102
|
-
|
|
84
|
+
Precedence: `pending-question.json` wins. Skip conditions (any disables): pending-question exists, ready empty, autoPickup off, marker absent.
|
|
103
85
|
|
|
104
|
-
Enforced by: `task-boundary-reset.js → writeCleanCompletionMarker()` + `session-context.js
|
|
86
|
+
Enforced by: `task-boundary-reset.js → writeCleanCompletionMarker()` + `session-context.js`. Marker: `.workflow/state/task-boundary-clean-completion.json` (single-use).
|
|
105
87
|
|
|
106
88
|
---
|
|
107
89
|
|
|
108
|
-
### Code Quality Patterns
|
|
90
|
+
### Code Quality Patterns
|
|
109
91
|
|
|
110
|
-
1. **Single
|
|
111
|
-
2. **Named
|
|
92
|
+
1. **Single source of truth for constants** — import from one canonical location.
|
|
93
|
+
2. **Named constants for magic numbers** — define thresholds as named constants; don't inline literals.
|
|
112
94
|
|
|
113
95
|
---
|
|
114
96
|
|
|
115
97
|
### Regression Discipline
|
|
116
98
|
|
|
117
|
-
Typecheck/lint/build
|
|
99
|
+
Typecheck/lint/build catches code errors, not behavior drift. For critical user-facing flows (login, submit, approve, delete, invite):
|
|
118
100
|
|
|
119
|
-
1.
|
|
120
|
-
2.
|
|
121
|
-
3.
|
|
122
|
-
4.
|
|
101
|
+
1. Executable scripts at `regression-suite/<flow>.<ext>`, not test-plan documents.
|
|
102
|
+
2. Living feature inventory: `Feature | Last Verified | Commit | Regression Script | Known Issues`.
|
|
103
|
+
3. Change-touch rule: task modifying a file mapped to a regression script must pass that script before close.
|
|
104
|
+
4. Audit-seeded inventory via `/wogi-audit`, then human-reviewed.
|
|
123
105
|
|
|
124
|
-
|
|
106
|
+
"Confident my fix won't break it" is not evidence.
|
|
125
107
|
|
|
126
108
|
---
|
|
127
109
|
|
|
128
110
|
### Memory-First Clarification
|
|
129
111
|
|
|
130
|
-
Before asking
|
|
131
|
-
|
|
132
|
-
When you must ask, cite what you checked: *"I read domain-model.md §Roles; it says X — does this apply to Y too?"* — not *"what's Y?"*
|
|
133
|
-
|
|
134
|
-
If artifacts don't exist yet, run `node scripts/flow-intent-bootstrap.js bootstrap` (or trigger via `/wogi-start` on any IGR-enabled task). A project without `domain-model.md` is a project where every domain question will be re-asked every session.
|
|
112
|
+
Before asking a product-domain question, check `.workflow/state/{product,domain-model,user-journeys,glossary}.md`. When you must ask, cite what you read: *"I read domain-model.md §Roles; it says X — does this apply to Y too?"* not *"what's Y?"*. If artifacts don't exist, run `node scripts/flow-intent-bootstrap.js bootstrap`.
|
|
135
113
|
|
|
136
114
|
---
|
|
137
115
|
|
|
138
116
|
### Source Fidelity Rule (Verbatim Source Preservation)
|
|
139
117
|
|
|
140
|
-
When a long-form user request becomes a spec
|
|
141
|
-
|
|
142
|
-
The lossy step in cross-session/cross-worker compression is almost always at the spec-authoring layer (manager summarizing user input into a "contract"). Downstream actors then build the summary's interpretation, missing items the user explicitly named. Adversary checks won't catch this because the adversary sees only the spec, not the original prompt.
|
|
143
|
-
|
|
144
|
-
**Mandatory structure for any spec or dispatch derived from a long user prompt** (>40 lines OR ≥5 discrete items):
|
|
145
|
-
|
|
146
|
-
1. **`## Original Request (verbatim)` block** — the user's prompt unmodified. Required at the top of the spec body.
|
|
147
|
-
|
|
148
|
-
2. **`## Item Manifest` block** — enumerated list reconciling every source item to either:
|
|
149
|
-
- A specific AC in the spec, OR
|
|
150
|
-
- An explicit `defer-with-reason: <user-cited reason>` entry. The deferral is the user's call, not the AI's. AI-judged "low priority" is NOT a valid reason.
|
|
151
|
-
|
|
152
|
-
3. **Channel-dispatch links the spec, not summarizes it.** Manager-to-worker channel messages that create work MUST include either the verbatim source OR a path to a saved spec file containing the verbatim source. Bare "summary contracts" sent without source link are forbidden.
|
|
118
|
+
When a long-form user request becomes a spec or channel-dispatch, the **verbatim source MUST be preserved alongside the structured derivation**. The lossy step is at the spec-authoring layer (manager summarizing user input); downstream actors then build the summary, missing items the user named.
|
|
153
119
|
|
|
154
|
-
|
|
120
|
+
Mandatory structure for any spec/dispatch derived from a long user prompt (>40 lines OR ≥5 items):
|
|
155
121
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
-
|
|
159
|
-
- *"The user's prompt was rambling; my summary is cleaner"* → WRONG. Cleanliness is not authority to filter user-named items.
|
|
160
|
-
- *"This is just an internal manager message; the user won't see it"* → WRONG. That's exactly when the lossy step happens; verbatim preservation is more important here, not less.
|
|
161
|
-
- *"The long-input gate already extracted the items"* → WRONG IF you don't pin its output as canonical and reconcile every spec against it.
|
|
122
|
+
1. **`## Original Request (verbatim)`** — user's prompt unmodified, top of spec body.
|
|
123
|
+
2. **`## Item Manifest`** — enumerated list reconciling every source item to a specific AC OR an explicit `defer-with-reason: <user-cited reason>`. AI-judged "low priority" is not a valid reason.
|
|
124
|
+
3. **Channel-dispatch links the spec, not summarizes it** — manager-to-worker messages MUST include verbatim source OR a path to a saved spec containing it. Bare "summary contracts" are forbidden.
|
|
162
125
|
|
|
163
|
-
|
|
126
|
+
Enforced by: Logic Constitution v3 sub-principle 11.6. Adversary blocks specs missing the block when source qualifies. Verifier: `node scripts/flow-source-fidelity.js check <spec-file>`. Worker fallback: `scripts/hooks/core/long-input-enforcement.js`.
|
|
164
127
|
|
|
165
128
|
---
|
|
166
129
|
|
|
167
130
|
### Cross-Story Integration Tier-3 Rule
|
|
168
131
|
|
|
169
|
-
When Story B layers
|
|
132
|
+
When Story B layers on infrastructure shipped by Story A, Story B's IGR pass MUST treat that infrastructure as an audited dependency. Within-module unit tests don't verify Story A's contract holds for Story B's usage.
|
|
170
133
|
|
|
171
|
-
|
|
134
|
+
Mandatory for layering stories:
|
|
172
135
|
|
|
173
|
-
1. **Architect
|
|
136
|
+
1. **Architect names upstream dependencies** — "Dependencies" section listing prior stories/commits + the specific contract relied on (interface, file format, transport, invariant). Quote the contract.
|
|
137
|
+
2. **Adversary challenges the dependency** — "What if Story A's invariant doesn't hold? What evidence proves the contract is intact for THIS usage?"
|
|
138
|
+
3. **At least one Tier-3 integration test** exercises the chain end-to-end. Mark `// regression-tier3`.
|
|
139
|
+
4. **Pre-release gate** verifies stacked coverage. Missing Tier-3 + stacked stories → block release.
|
|
174
140
|
|
|
175
|
-
|
|
141
|
+
Apply: `git log --oneline <prior-N-commits>` to identify dependencies; for each, write the contract; `grep -r "<interface>"` to verify HEAD; write the Tier-3 test BEFORE Story B's code.
|
|
176
142
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
4. **Pre-release gate verifies stacked coverage.** Before tagging a release, identify any commits that layer on prior commits in the same release. For each, confirm a Tier-3 integration test exists. Missing Tier-3 + stacked stories → block release.
|
|
180
|
-
|
|
181
|
-
**Why:** unit tests within a story boundary catch the story's own bugs but miss every regression where the story's correct behavior depends on a broken upstream. The 2026-04-26 incident (audit-channel-transport-001) was caused by exactly this gap: Story A stripped MCP servers including the workspace-channel transport itself; Story B layered task-completion routing on top; both stories' tests passed; manager dispatch silently failed in production. Self-IGR caught Story B's local correctness but missed that the upstream contract was broken.
|
|
182
|
-
|
|
183
|
-
**Anti-rationalization:**
|
|
184
|
-
- *"The upstream story has its own tests"* → WRONG. Their tests pin THEIR contract. Your Tier-3 test pins YOUR usage of their contract.
|
|
185
|
-
- *"It's expensive to set up an integration test"* → WRONG. The 2026-04-26 incident cost a v2.29.1 hot-fix release. Set up time amortizes; regression cost compounds.
|
|
186
|
-
- *"Self-IGR is enough; we don't need the actual adversary subagent"* → WRONG. Self-IGR pattern-matches on the same model that wrote the plan; the cross-story dependency is exactly the blind spot a different-model adversary catches.
|
|
187
|
-
|
|
188
|
-
**How to apply** (concrete checks for any layering story):
|
|
189
|
-
- `git log --oneline <prior-N-commits>` — which earlier work does this story sit on?
|
|
190
|
-
- For each, write the contract you're relying on: "Story A delivers X via Y."
|
|
191
|
-
- `grep -r "<Story A's interface>"` — is the contract still intact in HEAD?
|
|
192
|
-
- Write the Tier-3 test BEFORE writing Story B's code. If the test cannot be written without first standing up infrastructure that makes the integration verifiable, that's a signal the architecture needs that infrastructure too.
|
|
193
|
-
|
|
194
|
-
Enforced by: Logic Constitution v3 sub-principle 11.5 (Stacked-story integration verification). Pre-release gate consumes this signal before tagging.
|
|
143
|
+
Enforced by: Logic Constitution v3 sub-principle 11.5. Pre-release gate consumes this signal before tagging.
|
|
195
144
|
|
|
196
145
|
---
|
|
197
146
|
|
|
198
147
|
### Autonomous Walk-Away Mode
|
|
199
148
|
|
|
200
|
-
|
|
149
|
+
User says "go until you finish" / "autonomous mode" / "run this autonomously" / "don't bother me, just do it" → flag activates, AI runs without interruption. While active:
|
|
201
150
|
|
|
202
|
-
- **productBehavior / ux
|
|
203
|
-
- **engineering / naming / implementation** → decide autonomously, report in
|
|
151
|
+
- **productBehavior / ux** → append to `.workflow/state/question-queue.json` (do NOT ask). Render in end-of-run summary.
|
|
152
|
+
- **engineering / naming / implementation** → decide autonomously, report in summary.
|
|
204
153
|
- **infrastructure / performance** → decide autonomously, report after.
|
|
205
|
-
- **security** → auto-fix-report-after
|
|
206
|
-
- **low-confidence technical decisions** → self-adversarial challenge to ≥90% confidence; queue if cap hit. Counter
|
|
207
|
-
- **Blocking errors
|
|
154
|
+
- **security** → auto-fix-report-after.
|
|
155
|
+
- **low-confidence technical decisions** → self-adversarial challenge to ≥90% confidence; queue if cap hit. Counter shared with IGR Architect-Adversary loop (default cap 30, `autonomousMode.maxAdversaryInvocations`).
|
|
156
|
+
- **Blocking errors** → fix autonomously; surface only if fundamentally un-fixable.
|
|
208
157
|
|
|
209
|
-
|
|
158
|
+
Persistence: flag in `session-state.json`, survives task-boundary SIGTERM via SessionStart re-hydration. Staleness threshold (`autonomousMode.stalenessThresholdMs`, default 1h) — stale flags don't auto-resume.
|
|
210
159
|
|
|
211
|
-
|
|
160
|
+
Anti-hedging while active: "let me know if", "should I continue", "awaiting your signal", "standing by", "would you like me to" are forbidden.
|
|
212
161
|
|
|
213
|
-
|
|
162
|
+
Exit: ready drains, user types "stop"/"pause", or fatal error. On exit, render completion summary (`.workflow/state/autonomous-run-summary-<runId>.json`) and clear flag.
|
|
214
163
|
|
|
215
|
-
Enforced by: `flow-autonomous-detector.js`, `flow-question-queue.js`, `flow-decision-authority.js` (autonomous param + `queue-for-review` + `adversary-loop` buckets), `flow-completion-summary.js`,
|
|
164
|
+
Enforced by: `flow-autonomous-detector.js`, `flow-question-queue.js`, `flow-decision-authority.js` (autonomous param + `queue-for-review` + `adversary-loop` buckets), `flow-completion-summary.js`, SessionStart context in `session-context.js`.
|
|
216
165
|
|
|
217
166
|
---
|
|
218
167
|
|
|
219
168
|
### Mechanical Deferral Authorization Gate (wf-f9912af6)
|
|
220
169
|
|
|
221
|
-
The
|
|
222
|
-
|
|
223
|
-
1. The new content introduces one or more findings whose `status` matches `/^deferred(?:[-_].*)?$|^wont-?fix$|^skipped$/i`, AND
|
|
224
|
-
2. No valid authorization marker exists at `.workflow/state/deferral-authorization.json`, AND
|
|
225
|
-
3. The `no-defer-pin.json` is not active (a pin overrides any auth — set when the user says "fix everything" / "no deferrals" / "I don't want tech debt").
|
|
226
|
-
|
|
227
|
-
**Authorization sources** (one of):
|
|
228
|
-
|
|
229
|
-
- **User-prompt classifier** (`scripts/hooks/core/deferral-classifier.js`): regex-detects explicit defer phrases in UserPromptSubmit messages — "defer X", "fix critical only", "ship as-is", "option 2"/"option 4" from the /wogi-review menu, etc. Writes auth marker with TTL 10 min by default.
|
|
230
|
-
- **Explicit CLI**: `node scripts/flow-defer-auth.js grant --scope=all --reason="<verbatim user phrase>"` (or `--findings=F5,F6,...`). Used when the AI needs to record explicit authorization (e.g., user picked option 4).
|
|
170
|
+
The Review-Findings Anti-Deferral rule is enforced mechanically. The PreToolUse hook intercepts every Write/Edit/Bash that targets `.workflow/state/last-review.json` or `last-audit.json` and BLOCKS the write when:
|
|
231
171
|
|
|
232
|
-
|
|
172
|
+
1. New content introduces a finding with `status` matching `/^deferred(?:[-_].*)?$|^wont-?fix$|^skipped$/i`, AND
|
|
173
|
+
2. No valid auth marker at `.workflow/state/deferral-authorization.json`, AND
|
|
174
|
+
3. `no-defer-pin.json` is not active.
|
|
233
175
|
|
|
234
|
-
**
|
|
176
|
+
**Authorization sources**:
|
|
177
|
+
- **User-prompt classifier** — regex-detects defer phrases ("defer X", "fix critical only", "ship as-is", "option 2/4"). Auth TTL 10min.
|
|
178
|
+
- **Explicit CLI** — `node scripts/flow-defer-auth.js grant --scope=all --reason="<verbatim user phrase>"`.
|
|
235
179
|
|
|
236
|
-
**
|
|
180
|
+
**Negative intent overrides positive**: "fix everything", "no deferrals", "I don't want tech debt" delete auth and write `no-defer-pin.json` (~30min hard-block).
|
|
237
181
|
|
|
238
|
-
**
|
|
182
|
+
**Bash-mutating commands** that target review/audit files AND mention `deferred|wont-fix|skipped|dismissed` are blocked when no auth is active. Reads (cat/jq/grep) pass.
|
|
239
183
|
|
|
240
|
-
|
|
241
|
-
- *"This finding is pre-existing, not introduced by my changes"* → WRONG. Pre-existing is a reason to fix it now (continuous improvement) or to surface it to the user with an explicit "ship / fix / defer" question, not to silently `status: deferred-pre-existing`.
|
|
242
|
-
- *"This is LOW severity, the user won't care"* → WRONG. Severity is the user's call, not yours.
|
|
243
|
-
- *"The adversary already verified it's not a real bug"* → WRONG. If it's not a bug, mark it `dismissed-not-a-bug` only AFTER the user confirms; otherwise leave it `open`.
|
|
244
|
-
- *"I'll batch deferrals into the next review cycle"* → WRONG. There is no "next cycle" — the user reads the findings now.
|
|
184
|
+
Audit trail: `.workflow/state/deferral-block-log.json` (last 100). Config: `deferralGate.{enabled,authTtlSeconds,classifyUserPrompts}` (defaults true / 600 / true).
|
|
245
185
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
Enforced by: `scripts/hooks/core/deferral-gate.js` (core), `scripts/hooks/core/deferral-classifier.js` (intent detection), `scripts/flow-defer-auth.js` (CLI), wired into `scripts/hooks/core/pre-tool-orchestrator.js` (PreToolUse) and `scripts/hooks/entry/claude-code/user-prompt-submit.js` (UserPromptSubmit).
|
|
186
|
+
Enforced by: `scripts/hooks/core/deferral-gate.js`, `deferral-classifier.js`, `scripts/flow-defer-auth.js`, wired into `pre-tool-orchestrator.js` and `user-prompt-submit.js`.
|
|
249
187
|
|
|
250
188
|
---
|
|
251
189
|
|
|
252
190
|
### Mechanical Research-Required Gate (wf-5cd71b1f)
|
|
253
191
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
**How it works**:
|
|
257
|
-
|
|
258
|
-
1. **UserPromptSubmit classifier** (`scripts/hooks/core/research-required-classifier.js`): regex-classifies each prompt into `command` / `factual` / `diagnostic` / `none`.
|
|
259
|
-
- `command` — task IDs, action imperatives ("add X"), follow-ups ("yes", "continue", "option N"), AI's own slash commands
|
|
260
|
-
- `factual` — Tier 1 markers ("what is", "where is", "show me", "list all")
|
|
261
|
-
- `diagnostic` — Tier 2/3 markers ("why", "should I", "what do you think", "is this correct", "explain why", "did you fix")
|
|
262
|
-
- On `diagnostic`: writes `.workflow/state/research-required-this-turn.json` with `{requiredEvidence: 2, attemptCount: 0, classifiedAt}`.
|
|
263
|
-
|
|
264
|
-
2. **Override**: prompt prefix `!` skips the gate entirely. For when the user knows their question is conversational and doesn't need evidence reading.
|
|
265
|
-
|
|
266
|
-
3. **Stop-hook gate** (`scripts/hooks/core/research-required-gate.js`): if marker exists, parses the JSONL transcript for the current turn (since the most recent user entry), counts:
|
|
267
|
-
- `Read` tool calls where `file_path` matches an evidence prefix
|
|
268
|
-
- `Bash` tool calls where the command starts with `cat|head|tail|grep|rg|jq|less|view|awk|sed` and targets an evidence-prefix path
|
|
269
|
-
- `Glob`/`Grep` tool calls (any pattern counts)
|
|
270
|
-
|
|
271
|
-
4. **If count < requiredEvidence**:
|
|
272
|
-
- Increments `attemptCount` in the marker
|
|
273
|
-
- Returns `{continue: true, stopReason: <violation message>}` — Claude Code re-prompts the AI with the message; the AI must redo the turn with reads
|
|
274
|
-
- After `maxAttempts` (default 3): returns `{continue: false, stopReason: <hard-stop message>}` — visible to the user, marker cleared
|
|
275
|
-
|
|
276
|
-
5. **If count ≥ requiredEvidence**: marker is consumed (deleted), Stop proceeds normally.
|
|
192
|
+
Diagnostic prompts are intercepted at UserPromptSubmit and re-prompted at Stop hook if the assistant turn produced text without enough Read calls against evidence paths.
|
|
277
193
|
|
|
278
|
-
|
|
194
|
+
Flow:
|
|
279
195
|
|
|
280
|
-
**
|
|
196
|
+
1. **Classifier** (`research-required-classifier.js`) classifies each prompt: `command` / `factual` / `diagnostic` / `none`. Diagnostic markers: "why", "should I", "what do you think", "is this correct", "explain why", "did you fix". On diagnostic → write `.workflow/state/research-required-this-turn.json` with `{requiredEvidence: 2, attemptCount: 0}`.
|
|
197
|
+
2. **Override**: prompt prefix `!` skips the gate.
|
|
198
|
+
3. **Stop-hook gate** (`research-required-gate.js`) parses the JSONL transcript, counts Read against evidence prefixes, Bash with `cat|head|tail|grep|rg|jq|less|view|awk|sed` against evidence paths, and any Glob/Grep.
|
|
199
|
+
4. **count < required** → `{continue: true, stopReason: <message>}` forces redo. After `maxAttempts` (default 3) → hard-stop visible to user.
|
|
200
|
+
5. **count ≥ required** → marker consumed, Stop proceeds.
|
|
281
201
|
|
|
282
|
-
|
|
283
|
-
- *"I already know the answer from context"* → WRONG. Confidence is not evidence. The gate fires on the question's structure, not your perceived certainty.
|
|
284
|
-
- *"This question is conversational, doesn't need code reading"* → WRONG. If you genuinely believe that, the user can prefix `!` next time. Within a turn, the gate is final.
|
|
285
|
-
- *"I'll cite the evidence in my next answer instead of reading it now"* → WRONG. Citations require reads in the same turn. The transcript proves it.
|
|
202
|
+
Evidence prefixes: `.workflow/state/`, `.workflow/changes/`, `.workflow/specs/`, `.workflow/epics/`, `lib/`, `scripts/`, `src/`, `tests/`, `app/`.
|
|
286
203
|
|
|
287
|
-
Config: `researchRequiredGate.{enabled,requiredEvidence,maxAttempts}`
|
|
204
|
+
Config: `researchRequiredGate.{enabled,requiredEvidence,maxAttempts}` (defaults true / 2 / 3). Override prefix `!` is hard-coded.
|
|
288
205
|
|
|
289
|
-
Enforced by: `
|
|
206
|
+
Enforced by: `research-required-classifier.js` (UserPromptSubmit), `research-required-gate.js` (Stop), wired into `user-prompt-submit.js` and `stop.js`.
|
|
@@ -79,7 +79,7 @@ const PEERS = parsePeers(PEERS_RAW);
|
|
|
79
79
|
// Minimal MCP Protocol (JSON-RPC 2.0 over stdio)
|
|
80
80
|
// ============================================================
|
|
81
81
|
|
|
82
|
-
let
|
|
82
|
+
let _initialized = false;
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Send a JSON-RPC message to Claude Code via stdout.
|
|
@@ -198,7 +198,7 @@ function handleRequest(msg) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
if (msg.method === 'notifications/initialized') {
|
|
201
|
-
|
|
201
|
+
_initialized = true;
|
|
202
202
|
return;
|
|
203
203
|
}
|
|
204
204
|
|
|
@@ -20,6 +20,11 @@ const fs = require('node:fs');
|
|
|
20
20
|
const path = require('node:path');
|
|
21
21
|
const crypto = require('node:crypto');
|
|
22
22
|
|
|
23
|
+
// wf-3c968989: use safeJsonParse for prototype-pollution protection on
|
|
24
|
+
// the workspace manifest read. Throw-on-failure contract preserved below
|
|
25
|
+
// via explicit null check (manifest is mandatory for workspace mode).
|
|
26
|
+
const { safeJsonParse } = require('../scripts/flow-io');
|
|
27
|
+
|
|
23
28
|
const VALID_REPO_NAME = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
24
29
|
const VALID_TASK_ID = /^wf-[0-9a-f]{8}$/i;
|
|
25
30
|
const REQUIRED_FIELDS = ['id', 'title', 'type'];
|
|
@@ -42,11 +47,12 @@ function getWorkerReadyPath(workspaceRoot, repoName) {
|
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
const configPath = path.join(workspaceRoot, 'wogi-workspace.json');
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
// wf-3c968989: safeJsonParse adds DANGEROUS_KEYS protection. It returns
|
|
51
|
+
// null on missing/corrupt/array-typed input — manifest is mandatory, so
|
|
52
|
+
// we preserve the original throw-on-failure contract via null check.
|
|
53
|
+
const manifest = safeJsonParse(configPath, null);
|
|
54
|
+
if (!manifest) {
|
|
55
|
+
throw new Error(`Cannot read workspace manifest at ${configPath}`);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
const member = manifest.members?.[repoName];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wogiflow",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.8",
|
|
4
4
|
"description": "AI-powered development workflow management system with multi-model support",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"flow": "./scripts/flow",
|
|
13
|
-
"test": "NODE_ENV=test node --test tests/auto-compact-prompt.test.js tests/flow-paths.test.js tests/flow-io.test.js tests/flow-config-loader.test.js tests/flow-damage-control.test.js tests/flow-output.test.js tests/flow-constants.test.js tests/flow-session-state.test.js tests/flow-hooks-integration.test.js tests/flow-utils.test.js tests/flow-security.test.js tests/flow-memory-db.test.js tests/flow-durable-session.test.js tests/flow-skill-matcher.test.js tests/flow-bridge.test.js tests/flow-proactive-compact.test.js tests/flow-cascade-completion.test.js tests/flow-capture-gate.test.js tests/flow-correction-detector-hybrid.test.js tests/flow-promote.test.js tests/flow-archive-runs.test.js tests/flow-memory.test.js tests/flow-hooks-pre-tool-helpers.test.js tests/flow-hooks-bugfix-scope-gate.test.js tests/flow-hooks-routing-gate.test.js tests/flow-hooks-phase-read-gate.test.js tests/flow-hooks-commit-log-gate.test.js tests/flow-hooks-deploy-gate.test.js tests/flow-hooks-todowrite-gate.test.js tests/flow-hooks-git-safety-gate.test.js tests/flow-hooks-scope-mutation-gate.test.js tests/flow-hooks-strike-gate.test.js tests/flow-hooks-component-check.test.js tests/flow-hooks-scope-gate.test.js tests/flow-hooks-implementation-gate.test.js tests/flow-hooks-research-gate.test.js tests/flow-hooks-loop-check.test.js tests/flow-hooks-manager-boundary-gate.test.js tests/flow-hooks-phase-gate.test.js tests/flow-hooks-pre-tool-orchestrator.test.js tests/flow-hooks-observation-capture.test.js tests/flow-hooks-task-gate.test.js tests/flow-durable-session-suspension.test.js tests/flow-health-mcp-scopes.test.js tests/flow-lean-config.test.js tests/flow-workspace-autopickup.test.js tests/flow-worker-boundary-gate.test.js tests/flow-worker-question-classifier.test.js tests/flow-completion-truth-gate-contradictions.test.js tests/flow-structure-sensor.test.js tests/flow-workspace-dispatch-tracking.test.js tests/workspace-ipc-sqlite.test.js tests/workspace-ipc-multi-worker.test.js tests/flow-story-gates.test.js tests/flow-workspace-restart-handoff.test.js tests/flow-wogi-claude-wrapper.test.js tests/flow-wave1-integrations.test.js tests/flow-wave2-integrations.test.js tests/flow-wave3-integrations.test.js tests/flow-commit-claims-gate.test.js tests/auto-review.test.js tests/gate-telemetry-surface.test.js tests/agents-md-alias.test.js tests/flow-skill-manage.test.js tests/fuzzy-patch.test.js tests/mode-schema.test.js tests/flow-feature-dossier.test.js tests/flow-autonomous-mode.test.js tests/flow-epic-cascade.test.js tests/flow-workspace-summary.test.js tests/flow-hooks-research-evidence-gate.test.js tests/flow-worker-mcp-strip.test.js tests/flow-orchestrate-corrections.test.js tests/flow-source-fidelity.test.js tests/flow-hooks-long-input-enforcement.test.js tests/workspace-channel-tracking.test.js tests/flow-hooks-deletion-log.test.js tests/flow-task-boundary-reset.test.js tests/flow-deferral-gate.test.js tests/flow-research-required-gate.test.js && NODE_ENV=test node tests/run-quality-gates.test.js",
|
|
13
|
+
"test": "NODE_ENV=test node --test tests/auto-compact-prompt.test.js tests/flow-paths.test.js tests/flow-io.test.js tests/flow-audit-gates.test.js tests/flow-standards-hook-three-layer.test.js tests/flow-config-loader.test.js tests/flow-damage-control.test.js tests/flow-output.test.js tests/flow-constants.test.js tests/flow-session-state.test.js tests/flow-hooks-integration.test.js tests/flow-utils.test.js tests/flow-security.test.js tests/flow-memory-db.test.js tests/flow-durable-session.test.js tests/flow-skill-matcher.test.js tests/flow-bridge.test.js tests/flow-proactive-compact.test.js tests/flow-cascade-completion.test.js tests/flow-capture-gate.test.js tests/flow-correction-detector-hybrid.test.js tests/flow-promote.test.js tests/flow-archive-runs.test.js tests/flow-memory.test.js tests/flow-hooks-pre-tool-helpers.test.js tests/flow-hooks-bugfix-scope-gate.test.js tests/flow-hooks-routing-gate.test.js tests/flow-hooks-phase-read-gate.test.js tests/flow-hooks-commit-log-gate.test.js tests/flow-hooks-deploy-gate.test.js tests/flow-hooks-todowrite-gate.test.js tests/flow-hooks-git-safety-gate.test.js tests/flow-hooks-scope-mutation-gate.test.js tests/flow-hooks-strike-gate.test.js tests/flow-hooks-component-check.test.js tests/flow-hooks-scope-gate.test.js tests/flow-hooks-implementation-gate.test.js tests/flow-hooks-research-gate.test.js tests/flow-hooks-loop-check.test.js tests/flow-hooks-manager-boundary-gate.test.js tests/flow-hooks-phase-gate.test.js tests/flow-hooks-pre-tool-orchestrator.test.js tests/flow-hooks-observation-capture.test.js tests/flow-hooks-task-gate.test.js tests/flow-durable-session-suspension.test.js tests/flow-health-mcp-scopes.test.js tests/flow-lean-config.test.js tests/flow-workspace-autopickup.test.js tests/flow-worker-boundary-gate.test.js tests/flow-worker-question-classifier.test.js tests/flow-completion-truth-gate-contradictions.test.js tests/flow-structure-sensor.test.js tests/flow-workspace-dispatch-tracking.test.js tests/workspace-ipc-sqlite.test.js tests/workspace-ipc-multi-worker.test.js tests/flow-story-gates.test.js tests/flow-workspace-restart-handoff.test.js tests/flow-wogi-claude-wrapper.test.js tests/flow-wave1-integrations.test.js tests/flow-wave2-integrations.test.js tests/flow-wave3-integrations.test.js tests/flow-commit-claims-gate.test.js tests/auto-review.test.js tests/gate-telemetry-surface.test.js tests/agents-md-alias.test.js tests/flow-skill-manage.test.js tests/fuzzy-patch.test.js tests/mode-schema.test.js tests/flow-feature-dossier.test.js tests/flow-autonomous-mode.test.js tests/flow-epic-cascade.test.js tests/flow-workspace-summary.test.js tests/flow-hooks-research-evidence-gate.test.js tests/flow-worker-mcp-strip.test.js tests/flow-orchestrate-corrections.test.js tests/flow-source-fidelity.test.js tests/flow-hooks-long-input-enforcement.test.js tests/workspace-channel-tracking.test.js tests/flow-hooks-deletion-log.test.js tests/flow-task-boundary-reset.test.js tests/flow-deferral-gate.test.js tests/flow-research-required-gate.test.js && NODE_ENV=test node tests/run-quality-gates.test.js",
|
|
14
14
|
"test:syntax": "find scripts/ lib/ -name '*.js' -not -path '*/node_modules/*' -exec node --check {} +",
|
|
15
15
|
"lint": "eslint scripts/ lib/ tests/",
|
|
16
16
|
"lint:ci": "eslint scripts/ lib/ tests/ --max-warnings 0",
|
|
@@ -74,6 +74,9 @@
|
|
|
74
74
|
"engines": {
|
|
75
75
|
"node": ">=18.0.0"
|
|
76
76
|
},
|
|
77
|
+
"overrides": {
|
|
78
|
+
"protobufjs": ">=7.5.5"
|
|
79
|
+
},
|
|
77
80
|
"publishConfig": {
|
|
78
81
|
"access": "public"
|
|
79
82
|
}
|
|
@@ -282,19 +282,77 @@ function checkLintConfigIntegrity() {
|
|
|
282
282
|
return result;
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Parse test failure count from Node test runner stdout.
|
|
287
|
+
*
|
|
288
|
+
* Bug fixed 2026-05-08 (wf-e111d850): previously inherited the generic
|
|
289
|
+
* runProjectScript regex `/error TS\d+|Error:|ERROR/gi` which matched the
|
|
290
|
+
* substring "error" in passing test descriptions (e.g., 'trimRetryErrors',
|
|
291
|
+
* 'classifier error path', 'returns null on git unavailable / error'),
|
|
292
|
+
* inflating errorCount even when 0 tests actually failed. Compounded by
|
|
293
|
+
* Node test runner v22 sometimes exiting non-zero on all-pass.
|
|
294
|
+
*
|
|
295
|
+
* Strategy:
|
|
296
|
+
* 1. Primary — parse Node test runner "Results: N passed, M failed" summary
|
|
297
|
+
* lines (one per suite when running multiple files). Sum the M values.
|
|
298
|
+
* 2. Fallback — count TAP "not ok N" lines if no summary present.
|
|
299
|
+
* 3. Default — 0 (graceful) if neither parser finds anything.
|
|
300
|
+
*
|
|
301
|
+
* @param {string} output — combined stdout+stderr from `npm run test`
|
|
302
|
+
* @returns {{ errorCount: number, source: 'summary' | 'tap' | 'default' }}
|
|
303
|
+
*/
|
|
304
|
+
function parseTestErrorCount(output) {
|
|
305
|
+
if (typeof output !== 'string' || output.length === 0) {
|
|
306
|
+
return { errorCount: 0, source: 'default' };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Primary: Node test runner summary line(s).
|
|
310
|
+
// Format: "Results: N passed, M failed" (color codes already stripped via
|
|
311
|
+
// FORCE_COLOR=0 / NO_COLOR=1 in runProjectScript).
|
|
312
|
+
const summaryRe = /Results:\s*\d+\s*passed,\s*(\d+)\s*failed/gi;
|
|
313
|
+
let summaryFound = false;
|
|
314
|
+
let total = 0;
|
|
315
|
+
for (const m of output.matchAll(summaryRe)) {
|
|
316
|
+
summaryFound = true;
|
|
317
|
+
total += parseInt(m[1], 10) || 0;
|
|
318
|
+
}
|
|
319
|
+
if (summaryFound) return { errorCount: total, source: 'summary' };
|
|
320
|
+
|
|
321
|
+
// Fallback: TAP "not ok N - ..." line count
|
|
322
|
+
const tap = (output.match(/^not ok \d+/gm) || []).length;
|
|
323
|
+
if (tap > 0) return { errorCount: tap, source: 'tap' };
|
|
324
|
+
|
|
325
|
+
return { errorCount: 0, source: 'default' };
|
|
326
|
+
}
|
|
327
|
+
|
|
285
328
|
/**
|
|
286
329
|
* Gate: Tests — do tests pass?
|
|
330
|
+
*
|
|
331
|
+
* Uses parseTestErrorCount() to override the generic regex from
|
|
332
|
+
* runProjectScript. See parseTestErrorCount() comment for bug history.
|
|
287
333
|
*/
|
|
288
334
|
function checkTests() {
|
|
289
335
|
const result = runProjectScript('test', 120000);
|
|
336
|
+
const parseSource = result.rawOutput || result.output || '';
|
|
337
|
+
const { errorCount, source: parserSource } = parseTestErrorCount(parseSource);
|
|
338
|
+
|
|
339
|
+
// Trust the parser over npm exit code: Node test runner v22 can exit
|
|
340
|
+
// non-zero in some configurations even when all tests pass. If the parser
|
|
341
|
+
// finds 0 failures via the summary line, that's authoritative.
|
|
342
|
+
const passed = errorCount === 0;
|
|
343
|
+
|
|
290
344
|
return {
|
|
291
345
|
gate: 'tests',
|
|
292
346
|
...result,
|
|
347
|
+
errorCount,
|
|
348
|
+
passed,
|
|
349
|
+
parserSource,
|
|
293
350
|
scoreCap: 100, // Test failure doesn't cap, but is a HIGH finding
|
|
294
351
|
severity: !result.exists ? 'info' :
|
|
295
|
-
|
|
352
|
+
passed ? 'pass' : 'high',
|
|
296
353
|
message: !result.exists ? 'No test script defined' :
|
|
297
|
-
|
|
354
|
+
passed ? 'Tests pass' :
|
|
355
|
+
`Tests FAIL: ${errorCount} failure(s)`
|
|
298
356
|
};
|
|
299
357
|
}
|
|
300
358
|
|
|
@@ -767,6 +825,7 @@ module.exports = {
|
|
|
767
825
|
checkLint,
|
|
768
826
|
checkLintConfigIntegrity,
|
|
769
827
|
checkTests,
|
|
828
|
+
parseTestErrorCount, // wf-e111d850: exposed for unit testing
|
|
770
829
|
checkScriptCompleteness,
|
|
771
830
|
|
|
772
831
|
// Extended checks
|
|
@@ -1038,6 +1038,24 @@ const CONFIG_DEFAULTS = {
|
|
|
1038
1038
|
claudeCode: { installPath: '.claude/settings.local.json' }
|
|
1039
1039
|
},
|
|
1040
1040
|
|
|
1041
|
+
// --- Standards Check (wf-00c5067b) ---
|
|
1042
|
+
// Hook three-layer enforcement: entry files ≤120 LOC + ≤2 core/ imports.
|
|
1043
|
+
// Exemption list documents pre-extraction violators; clear each entry as
|
|
1044
|
+
// its corresponding Phase 2 task ships.
|
|
1045
|
+
standardsCheck: {
|
|
1046
|
+
hookThreeLayer: {
|
|
1047
|
+
enabled: true,
|
|
1048
|
+
maxLoc: 120,
|
|
1049
|
+
maxCoreImports: 2,
|
|
1050
|
+
exemptions: {
|
|
1051
|
+
'scripts/hooks/entry/claude-code/stop.js': 'Phase 2 — wf-c1e892fa entry-file extraction (orchestration logic to core); remove exemption when extracted',
|
|
1052
|
+
'scripts/hooks/entry/claude-code/session-start.js': 'Phase 2 — wf-c1e892fa entry-file extraction (orchestration logic to core); remove exemption when extracted',
|
|
1053
|
+
'scripts/hooks/entry/claude-code/user-prompt-submit.js': 'Phase 2 — wf-c1e892fa entry-file extraction (orchestration logic to core); remove exemption when extracted',
|
|
1054
|
+
'scripts/hooks/entry/claude-code/post-tool-use.js': 'Phase 2 — wf-c1e892fa entry-file extraction (orchestration logic to core); remove exemption when extracted'
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1041
1059
|
// --- Metrics ---
|
|
1042
1060
|
metrics: { enabled: false },
|
|
1043
1061
|
|
|
@@ -20,7 +20,7 @@ const { PATHS, getConfig, readJson, success } = require('./flow-utils');
|
|
|
20
20
|
|
|
21
21
|
// Default to PATHS.root from flow-utils, can be overridden via setProjectRoot() or CLI arg
|
|
22
22
|
let PROJECT_ROOT = PATHS.root;
|
|
23
|
-
let
|
|
23
|
+
let _CONFIG_PATH = path.join(PROJECT_ROOT, '.workflow/config.json');
|
|
24
24
|
let CACHE_PATH = path.join(PROJECT_ROOT, '.workflow/state/export-map.json');
|
|
25
25
|
const CACHE_MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
26
26
|
|
|
@@ -31,7 +31,7 @@ const CACHE_MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
31
31
|
*/
|
|
32
32
|
function setProjectRoot(root) {
|
|
33
33
|
PROJECT_ROOT = path.resolve(root);
|
|
34
|
-
|
|
34
|
+
_CONFIG_PATH = path.join(PROJECT_ROOT, '.workflow/config.json');
|
|
35
35
|
CACHE_PATH = path.join(PROJECT_ROOT, '.workflow/state/export-map.json');
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -700,7 +700,7 @@ function formatExportMapForTemplate(exportMap) {
|
|
|
700
700
|
// Components
|
|
701
701
|
if (Object.keys(exportMap.components).length > 0) {
|
|
702
702
|
lines.push('#### Components');
|
|
703
|
-
for (const [
|
|
703
|
+
for (const [_name, info] of Object.entries(exportMap.components)) {
|
|
704
704
|
const exports = info.exports.join(', ') || (info.defaultExport ? `default: ${info.defaultExport}` : '');
|
|
705
705
|
if (exports) {
|
|
706
706
|
lines.push(`- \`import { ${info.exports.join(', ')} } from '${info.importPath}'\``);
|
|
@@ -712,7 +712,7 @@ function formatExportMapForTemplate(exportMap) {
|
|
|
712
712
|
// Hooks
|
|
713
713
|
if (Object.keys(exportMap.hooks).length > 0) {
|
|
714
714
|
lines.push('#### Hooks');
|
|
715
|
-
for (const [
|
|
715
|
+
for (const [_name, info] of Object.entries(exportMap.hooks)) {
|
|
716
716
|
const exports = info.exports.join(', ');
|
|
717
717
|
if (exports) {
|
|
718
718
|
lines.push(`- \`import { ${exports} } from '${info.importPath}'\``);
|
|
@@ -724,7 +724,7 @@ function formatExportMapForTemplate(exportMap) {
|
|
|
724
724
|
// Services
|
|
725
725
|
if (Object.keys(exportMap.services).length > 0) {
|
|
726
726
|
lines.push('#### Services');
|
|
727
|
-
for (const [
|
|
727
|
+
for (const [_name, info] of Object.entries(exportMap.services)) {
|
|
728
728
|
const exports = info.exports.join(', ');
|
|
729
729
|
if (exports) {
|
|
730
730
|
lines.push(`- \`import { ${exports} } from '${info.importPath}'\``);
|
|
@@ -736,7 +736,7 @@ function formatExportMapForTemplate(exportMap) {
|
|
|
736
736
|
// Types
|
|
737
737
|
if (Object.keys(exportMap.types).length > 0) {
|
|
738
738
|
lines.push('#### Types');
|
|
739
|
-
for (const [
|
|
739
|
+
for (const [_name, info] of Object.entries(exportMap.types)) {
|
|
740
740
|
const types = info.types.join(', ');
|
|
741
741
|
if (types) {
|
|
742
742
|
lines.push(`- \`import type { ${types} } from '${info.importPath}'\``);
|
|
@@ -748,7 +748,7 @@ function formatExportMapForTemplate(exportMap) {
|
|
|
748
748
|
// Utils
|
|
749
749
|
if (Object.keys(exportMap.utils).length > 0) {
|
|
750
750
|
lines.push('#### Utilities');
|
|
751
|
-
for (const [
|
|
751
|
+
for (const [_name, info] of Object.entries(exportMap.utils)) {
|
|
752
752
|
const exports = info.exports.join(', ');
|
|
753
753
|
if (exports) {
|
|
754
754
|
lines.push(`- \`import { ${exports} } from '${info.importPath}'\``);
|
|
@@ -784,7 +784,7 @@ function validateComponentUsage(code, exportMap = null) {
|
|
|
784
784
|
|
|
785
785
|
// Collect all array exports from components
|
|
786
786
|
const arrayExports = new Set();
|
|
787
|
-
for (const [
|
|
787
|
+
for (const [_name, info] of Object.entries(exportMap.components || {})) {
|
|
788
788
|
if (info.arrayExports) {
|
|
789
789
|
info.arrayExports.forEach(e => arrayExports.add(e));
|
|
790
790
|
}
|
|
@@ -841,7 +841,7 @@ function validateComponentUsage(code, exportMap = null) {
|
|
|
841
841
|
// Check if the actual export exists
|
|
842
842
|
const wrongName = pattern.source.replace(/\\/g, '').replace(/\(\)/g, '');
|
|
843
843
|
let found = false;
|
|
844
|
-
for (const [
|
|
844
|
+
for (const [_name, info] of Object.entries(exportMap.hooks || {})) {
|
|
845
845
|
if (info.exports?.includes(wrongName)) {
|
|
846
846
|
found = true;
|
|
847
847
|
break;
|
|
@@ -572,10 +572,12 @@ async function main() {
|
|
|
572
572
|
const matcher = new SimilarityMatcher(registry);
|
|
573
573
|
|
|
574
574
|
// Parse threshold argument
|
|
575
|
-
|
|
575
|
+
// _threshold: parsed from --threshold CLI arg but not currently passed to
|
|
576
|
+
// the matcher (real bug; see audit notes; out of scope for lint cleanup).
|
|
577
|
+
let _threshold = MATCH_CONFIG.thresholds.VARIANT_CANDIDATE;
|
|
576
578
|
const thresholdIndex = args.indexOf('--threshold');
|
|
577
579
|
if (thresholdIndex !== -1 && args[thresholdIndex + 1]) {
|
|
578
|
-
|
|
580
|
+
_threshold = parseInt(args[thresholdIndex + 1]);
|
|
579
581
|
}
|
|
580
582
|
|
|
581
583
|
if (input === '--stdin') {
|
|
@@ -32,12 +32,13 @@ try {
|
|
|
32
32
|
adaptiveLearning = null;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// Import error recovery for integration
|
|
36
|
-
|
|
35
|
+
// Import error recovery for integration (currently loaded for side-effect /
|
|
36
|
+
// future-use; not yet referenced — _ prefix per naming convention).
|
|
37
|
+
let _errorRecovery;
|
|
37
38
|
try {
|
|
38
|
-
|
|
39
|
+
_errorRecovery = require('./flow-error-recovery');
|
|
39
40
|
} catch (_err) {
|
|
40
|
-
|
|
41
|
+
_errorRecovery = null;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
// ============================================================
|
|
@@ -38,10 +38,10 @@ const { loadRegistry, loadStats } = require('./flow-model-types');
|
|
|
38
38
|
|
|
39
39
|
// Smart Context System integration
|
|
40
40
|
let contextGatherer = null;
|
|
41
|
-
let
|
|
41
|
+
let _instructionRichness = null;
|
|
42
42
|
try {
|
|
43
43
|
contextGatherer = require('./flow-context-gatherer');
|
|
44
|
-
|
|
44
|
+
_instructionRichness = require('./flow-instruction-richness');
|
|
45
45
|
} catch (_err) {
|
|
46
46
|
// Smart Context modules not available
|
|
47
47
|
}
|
|
@@ -17,8 +17,7 @@ const fs = require('node:fs');
|
|
|
17
17
|
const path = require('node:path');
|
|
18
18
|
const {
|
|
19
19
|
getConfig,
|
|
20
|
-
PATHS
|
|
21
|
-
fileExists
|
|
20
|
+
PATHS
|
|
22
21
|
} = require('./flow-utils');
|
|
23
22
|
|
|
24
23
|
// ============================================================
|
|
@@ -59,7 +58,7 @@ function parseSimpleYaml(content) {
|
|
|
59
58
|
const lines = content.split('\n');
|
|
60
59
|
let currentKey = null;
|
|
61
60
|
let currentSection = null;
|
|
62
|
-
let
|
|
61
|
+
let _currentList = null;
|
|
63
62
|
let indentLevel = 0;
|
|
64
63
|
let multilineValue = '';
|
|
65
64
|
let inMultiline = false;
|
|
@@ -100,7 +99,7 @@ function parseSimpleYaml(content) {
|
|
|
100
99
|
if (BLOCKED_KEYS.has(key)) continue;
|
|
101
100
|
|
|
102
101
|
currentSection = null;
|
|
103
|
-
|
|
102
|
+
_currentList = null;
|
|
104
103
|
|
|
105
104
|
if (value === '' || value === '|') {
|
|
106
105
|
// Start of nested section or multi-line
|
|
@@ -128,7 +127,7 @@ function parseSimpleYaml(content) {
|
|
|
128
127
|
|
|
129
128
|
if (BLOCKED_KEYS.has(key)) continue;
|
|
130
129
|
|
|
131
|
-
|
|
130
|
+
_currentList = null;
|
|
132
131
|
currentKey = key;
|
|
133
132
|
|
|
134
133
|
if (value === '|') {
|
package/scripts/flow-repo-map.js
CHANGED
|
@@ -23,6 +23,7 @@ const { execFileSync } = require('node:child_process');
|
|
|
23
23
|
|
|
24
24
|
const { PATHS } = require('./flow-paths');
|
|
25
25
|
const { getConfig } = require('./flow-config-loader');
|
|
26
|
+
const { safeJsonParse } = require('./flow-io');
|
|
26
27
|
|
|
27
28
|
const DEFAULT_BUDGET_BYTES = 16 * 1024; // ~4k tokens
|
|
28
29
|
const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.workflow', '.worktrees', 'out']);
|
|
@@ -46,12 +47,15 @@ function resolveChangedFiles(opts = {}) {
|
|
|
46
47
|
if (Array.isArray(opts.changedFiles)) return opts.changedFiles;
|
|
47
48
|
|
|
48
49
|
// Try task-checkpoint.json
|
|
50
|
+
// wf-3c968989: safeJsonParse adds DANGEROUS_KEYS protection. Returns null
|
|
51
|
+
// (the explicit default) on missing/corrupt/array — preserves the
|
|
52
|
+
// original silent-fallthrough contract via the null check below.
|
|
49
53
|
const checkpointPath = path.join(PATHS.state, 'task-checkpoint.json');
|
|
50
54
|
if (fs.existsSync(checkpointPath)) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
+
const cp = safeJsonParse(checkpointPath, null);
|
|
56
|
+
if (cp && Array.isArray(cp.changedFiles) && cp.changedFiles.length > 0) {
|
|
57
|
+
return cp.changedFiles;
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
// Git diff
|
|
@@ -20,7 +20,8 @@ const {
|
|
|
20
20
|
fileExists,
|
|
21
21
|
readFile,
|
|
22
22
|
safeJsonParse,
|
|
23
|
-
color
|
|
23
|
+
color,
|
|
24
|
+
getConfig
|
|
24
25
|
} = require('./flow-utils');
|
|
25
26
|
const {
|
|
26
27
|
calculateCombinedSimilarity,
|
|
@@ -76,19 +77,23 @@ const MATCH_LEVEL_SEVERITY = {
|
|
|
76
77
|
};
|
|
77
78
|
|
|
78
79
|
// Task type to check type mapping for smart scoping
|
|
80
|
+
// wf-00c5067b: 'hook-three-layer' added to all task types — entry-file LOC
|
|
81
|
+
// + import-count rule (per .claude/rules/architecture/hook-three-layer.md)
|
|
82
|
+
// is universally applicable; the exemption list in config covers known
|
|
83
|
+
// pre-extraction violators (see ARCH-001, ARCH-002 in .workflow/state/last-audit.json).
|
|
79
84
|
const TASK_CHECK_MAP = {
|
|
80
|
-
'component': ['naming', 'components', 'security'],
|
|
81
|
-
'utility': ['naming', 'functions', 'security'],
|
|
82
|
-
'api': ['naming', 'api', 'security'],
|
|
83
|
-
'feature': ['naming', 'components', 'functions', 'api', 'schemas', 'services', 'security'],
|
|
84
|
-
'bugfix': ['naming', 'security'],
|
|
85
|
-
'refactor': ['naming', 'components', 'functions', 'api', 'schemas', 'services', 'security'],
|
|
86
|
-
'story': ['naming', 'components', 'functions', 'api', 'schemas', 'services', 'security'],
|
|
87
|
-
'default': ['naming', 'components', 'functions', 'api', 'schemas', 'services', 'security']
|
|
85
|
+
'component': ['naming', 'components', 'security', 'hook-three-layer'],
|
|
86
|
+
'utility': ['naming', 'functions', 'security', 'hook-three-layer'],
|
|
87
|
+
'api': ['naming', 'api', 'security', 'hook-three-layer'],
|
|
88
|
+
'feature': ['naming', 'components', 'functions', 'api', 'schemas', 'services', 'security', 'hook-three-layer'],
|
|
89
|
+
'bugfix': ['naming', 'security', 'hook-three-layer'],
|
|
90
|
+
'refactor': ['naming', 'components', 'functions', 'api', 'schemas', 'services', 'security', 'hook-three-layer'],
|
|
91
|
+
'story': ['naming', 'components', 'functions', 'api', 'schemas', 'services', 'security', 'hook-three-layer'],
|
|
92
|
+
'default': ['naming', 'components', 'functions', 'api', 'schemas', 'services', 'security', 'hook-three-layer']
|
|
88
93
|
};
|
|
89
94
|
|
|
90
95
|
// All available check types
|
|
91
|
-
const ALL_CHECK_TYPES = ['naming', 'components', 'functions', 'api', 'schemas', 'services', 'security'];
|
|
96
|
+
const ALL_CHECK_TYPES = ['naming', 'components', 'functions', 'api', 'schemas', 'services', 'security', 'hook-three-layer'];
|
|
92
97
|
|
|
93
98
|
// ============================================================================
|
|
94
99
|
// Parse Standards Files
|
|
@@ -552,6 +557,88 @@ function checkSecurityPatterns(file, _securityRules) {
|
|
|
552
557
|
* @param {Object} matchConfig - Semantic match config — optional, auto-loaded if omitted
|
|
553
558
|
* @returns {Object[]} Array of violations
|
|
554
559
|
*/
|
|
560
|
+
/**
|
|
561
|
+
* wf-00c5067b — Hook Three-Layer enforcement.
|
|
562
|
+
*
|
|
563
|
+
* Per `.claude/rules/architecture/hook-three-layer.md`:
|
|
564
|
+
* - Entry files (`scripts/hooks/entry/<cli>/*.js`) must be ≤120 LOC and
|
|
565
|
+
* import from at most 2 `core/` modules (single-entry-point principle).
|
|
566
|
+
* - Core files (`scripts/hooks/core/*.js`) should be CLI-agnostic.
|
|
567
|
+
*
|
|
568
|
+
* This check enforces the LOC + import-count rules. Core CLI-identifier
|
|
569
|
+
* grep is intentionally NOT enforced here (false-positive prone — adversary
|
|
570
|
+
* critique 2026-05-08 found 1/4 supposed violations was actually config data).
|
|
571
|
+
*
|
|
572
|
+
* Exemptions: read from config.standardsCheck.hookThreeLayer.exemptions
|
|
573
|
+
* map of `{relativePath: reason}`. Each exemption MUST cite a rationale
|
|
574
|
+
* (typically a Phase 2 task ID for entries awaiting orchestrator extraction).
|
|
575
|
+
*
|
|
576
|
+
* @param {Object} file - File with path and content
|
|
577
|
+
* @param {Object} hookThreeLayerConfig - {enabled, exemptions, maxLoc, maxCoreImports}
|
|
578
|
+
* @returns {Object[]} Array of violations
|
|
579
|
+
*/
|
|
580
|
+
function checkHookThreeLayer(file, hookThreeLayerConfig = {}) {
|
|
581
|
+
const violations = [];
|
|
582
|
+
const {
|
|
583
|
+
enabled = true,
|
|
584
|
+
exemptions = {},
|
|
585
|
+
maxLoc = 120,
|
|
586
|
+
maxCoreImports = 2
|
|
587
|
+
} = hookThreeLayerConfig;
|
|
588
|
+
|
|
589
|
+
if (!enabled) return violations;
|
|
590
|
+
|
|
591
|
+
// Normalize path to repo-root-relative form for exemption lookup
|
|
592
|
+
const relPath = file.path.startsWith('/')
|
|
593
|
+
? path.relative(PATHS.root, file.path)
|
|
594
|
+
: file.path;
|
|
595
|
+
|
|
596
|
+
// Only apply to hook entry files
|
|
597
|
+
const isEntry = /^scripts\/hooks\/entry\/[^/]+\/[^/]+\.js$/.test(relPath);
|
|
598
|
+
if (!isEntry) return violations;
|
|
599
|
+
|
|
600
|
+
// Skip if exempted (with rationale)
|
|
601
|
+
if (Object.prototype.hasOwnProperty.call(exemptions, relPath)) return violations;
|
|
602
|
+
|
|
603
|
+
const content = file.content || '';
|
|
604
|
+
const lines = content.split('\n');
|
|
605
|
+
|
|
606
|
+
// Rule 1: LOC ceiling
|
|
607
|
+
if (lines.length > maxLoc) {
|
|
608
|
+
violations.push({
|
|
609
|
+
type: 'hook-three-layer',
|
|
610
|
+
severity: 'must-fix',
|
|
611
|
+
file: file.path,
|
|
612
|
+
line: null,
|
|
613
|
+
message: `Hook entry file exceeds ${maxLoc} LOC (${lines.length} lines). Extract orchestration logic to core/. Add to config.standardsCheck.hookThreeLayer.exemptions with rationale to defer.`,
|
|
614
|
+
rule: 'hook-three-layer.md'
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Rule 2: Core import count
|
|
619
|
+
// Match `require('../core/...')` or `require('../../core/...')` etc.
|
|
620
|
+
// Capture each core path; count distinct core modules imported.
|
|
621
|
+
const coreImportRegex = /require\(['"][^'"]*\/core\/([^'"/]+)['"]\)/g;
|
|
622
|
+
const coreModules = new Set();
|
|
623
|
+
let match;
|
|
624
|
+
while ((match = coreImportRegex.exec(content)) !== null) {
|
|
625
|
+
coreModules.add(match[1]);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (coreModules.size > maxCoreImports) {
|
|
629
|
+
violations.push({
|
|
630
|
+
type: 'hook-three-layer',
|
|
631
|
+
severity: 'must-fix',
|
|
632
|
+
file: file.path,
|
|
633
|
+
line: null,
|
|
634
|
+
message: `Hook entry imports from ${coreModules.size} core/ modules (limit: ${maxCoreImports}). Single-entry-point principle violated. Refactor to dispatch through one orchestrator-core. Modules: ${[...coreModules].sort().join(', ')}`,
|
|
635
|
+
rule: 'hook-three-layer.md'
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return violations;
|
|
640
|
+
}
|
|
641
|
+
|
|
555
642
|
function checkApiDuplication(file, existingEndpoints, matchConfig) {
|
|
556
643
|
const violations = [];
|
|
557
644
|
const content = file.content || '';
|
|
@@ -1009,6 +1096,16 @@ function runStandardsCheck(files, options = {}) {
|
|
|
1009
1096
|
const services = checksToRun.includes('services') ? parseServiceMap() : [];
|
|
1010
1097
|
|
|
1011
1098
|
const allViolations = [];
|
|
1099
|
+
|
|
1100
|
+
// wf-00c5067b: load hook-three-layer config (with sensible defaults if unset)
|
|
1101
|
+
const config = getConfig();
|
|
1102
|
+
const hookThreeLayerConfig = (config?.standardsCheck?.hookThreeLayer) || {
|
|
1103
|
+
enabled: checksToRun.includes('hook-three-layer'),
|
|
1104
|
+
exemptions: {},
|
|
1105
|
+
maxLoc: 120,
|
|
1106
|
+
maxCoreImports: 2
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1012
1109
|
const checksSummary = {
|
|
1013
1110
|
'decisions.md': { checked: true, violations: 0 },
|
|
1014
1111
|
'app-map.md': { checked: checksToRun.includes('components') && components.length > 0, violations: 0 },
|
|
@@ -1017,7 +1114,8 @@ function runStandardsCheck(files, options = {}) {
|
|
|
1017
1114
|
'schema-map.md': { checked: checksToRun.includes('schemas') && schemas.length > 0, violations: 0 },
|
|
1018
1115
|
'service-map.md': { checked: checksToRun.includes('services') && services.length > 0, violations: 0 },
|
|
1019
1116
|
'naming-conventions': { checked: checksToRun.includes('naming'), violations: 0 },
|
|
1020
|
-
'security-patterns': { checked: checksToRun.includes('security'), violations: 0 }
|
|
1117
|
+
'security-patterns': { checked: checksToRun.includes('security'), violations: 0 },
|
|
1118
|
+
'hook-three-layer': { checked: checksToRun.includes('hook-three-layer'), violations: 0 }
|
|
1021
1119
|
};
|
|
1022
1120
|
|
|
1023
1121
|
for (const file of files) {
|
|
@@ -1076,6 +1174,13 @@ function runStandardsCheck(files, options = {}) {
|
|
|
1076
1174
|
allViolations.push(...securityViolations);
|
|
1077
1175
|
checksSummary['security-patterns'].violations += securityViolations.length;
|
|
1078
1176
|
}
|
|
1177
|
+
|
|
1178
|
+
// Hook three-layer architecture (wf-00c5067b)
|
|
1179
|
+
if (checksToRun.includes('hook-three-layer')) {
|
|
1180
|
+
const hookViolations = checkHookThreeLayer(file, hookThreeLayerConfig);
|
|
1181
|
+
allViolations.push(...hookViolations);
|
|
1182
|
+
checksSummary['hook-three-layer'].violations += hookViolations.length;
|
|
1183
|
+
}
|
|
1079
1184
|
}
|
|
1080
1185
|
|
|
1081
1186
|
// Count must-fix violations
|
|
@@ -1309,6 +1414,7 @@ module.exports = {
|
|
|
1309
1414
|
checkApiDuplication,
|
|
1310
1415
|
checkRegistryDuplication,
|
|
1311
1416
|
checkSecurityPatterns,
|
|
1417
|
+
checkHookThreeLayer,
|
|
1312
1418
|
extractDeclaredNames,
|
|
1313
1419
|
discoverAllRegistries,
|
|
1314
1420
|
collectReuseCandidates,
|
|
@@ -192,7 +192,7 @@ function isAuthorized(deferralChanges) {
|
|
|
192
192
|
return { authorized: false, reason: 'auth-malformed-scope' };
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
function consumeAuth(
|
|
195
|
+
function consumeAuth(_deferralChanges) {
|
|
196
196
|
// Auth is single-use: once a deferral write succeeds, the marker is removed
|
|
197
197
|
// to prevent reuse on subsequent unrelated deferrals.
|
|
198
198
|
clearAuth();
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
const fs = require('node:fs');
|
|
47
47
|
const path = require('node:path');
|
|
48
48
|
const { PATHS } = require('../../flow-utils');
|
|
49
|
+
const { safeJsonParse } = require('../../flow-io');
|
|
49
50
|
|
|
50
51
|
const PENDING_PATH = path.join(PATHS.state, 'long-input-pending.json');
|
|
51
52
|
|
|
@@ -210,10 +211,10 @@ function isLongInputPending() {
|
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
function readLongInputPending() {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
// wf-3c968989: safeJsonParse adds DANGEROUS_KEYS protection. Returns null
|
|
215
|
+
// on missing/corrupt/array input — exact behavior match for the prior
|
|
216
|
+
// try/catch + return-null contract.
|
|
217
|
+
return safeJsonParse(PENDING_PATH, null);
|
|
217
218
|
}
|
|
218
219
|
|
|
219
220
|
/**
|