wogiflow 2.17.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/wogi-audit.md +212 -17
- package/.claude/commands/wogi-research.md +37 -0
- package/.claude/commands/wogi-review.md +200 -22
- package/.claude/commands/wogi-start.md +45 -0
- package/.claude/docs/claude-code-compatibility.md +46 -1
- package/.claude/docs/intent-grounded-review.md +209 -0
- package/.claude/settings.json +34 -1
- package/.workflow/agents/logic-adversary.md +8 -0
- package/.workflow/templates/claude-md.hbs +18 -0
- package/lib/installer.js +22 -0
- package/lib/utils.js +29 -3
- package/lib/workspace-changelog.js +2 -1
- package/lib/workspace-channel-server.js +4 -6
- package/lib/workspace-contracts.js +5 -4
- package/lib/workspace-events.js +8 -7
- package/lib/workspace-gates.js +4 -3
- package/lib/workspace-integration-tests.js +2 -1
- package/lib/workspace-intelligence.js +3 -2
- package/lib/workspace-locks.js +2 -1
- package/lib/workspace-messages.js +7 -6
- package/lib/workspace-routing.js +14 -26
- package/lib/workspace-session.js +7 -6
- package/lib/workspace-sync.js +9 -8
- package/package.json +4 -2
- package/scripts/base-workflow-step.js +1 -1
- package/scripts/flow +19 -0
- package/scripts/flow-adaptive-learning.js +1 -1
- package/scripts/flow-aggregate.js +2 -1
- package/scripts/flow-architect-pass.js +3 -3
- package/scripts/flow-archive-runs.js +372 -0
- package/scripts/flow-ask.js +1 -1
- package/scripts/flow-ast-grep.js +216 -0
- package/scripts/flow-audit-gates.js +1 -1
- package/scripts/flow-auto-learn.js +8 -11
- package/scripts/flow-bug.js +2 -2
- package/scripts/flow-capture-gate.js +644 -0
- package/scripts/flow-capture.js +4 -3
- package/scripts/flow-cli-flags.js +95 -0
- package/scripts/flow-community-sync.js +2 -1
- package/scripts/flow-community.js +6 -6
- package/scripts/flow-conclusion-classifier.js +310 -0
- package/scripts/flow-config-defaults.js +3 -3
- package/scripts/flow-constants.js +8 -11
- package/scripts/flow-context-scoring.js +1 -0
- package/scripts/flow-correction-detector.js +344 -3
- package/scripts/flow-damage-control.js +1 -1
- package/scripts/flow-decisions-merge.js +1 -0
- package/scripts/flow-done-gates.js +20 -0
- package/scripts/flow-done-report.js +2 -2
- package/scripts/flow-done.js +4 -4
- package/scripts/flow-epics.js +5 -11
- package/scripts/flow-health.js +145 -1
- package/scripts/flow-id.js +92 -0
- package/scripts/flow-io.js +15 -5
- package/scripts/flow-knowledge-router.js +2 -1
- package/scripts/flow-links.js +1 -1
- package/scripts/flow-log-manager.js +2 -1
- package/scripts/flow-logic-adversary.js +4 -4
- package/scripts/flow-long-input-cli.js +6 -0
- package/scripts/flow-long-input-stories.js +1 -1
- package/scripts/flow-loop-retry-learning.js +1 -1
- package/scripts/flow-mcp-capabilities.js +2 -3
- package/scripts/flow-mcp-docs.js +2 -1
- package/scripts/flow-memory-blocks.js +2 -1
- package/scripts/flow-memory-sync.js +1 -1
- package/scripts/flow-memory.js +767 -0
- package/scripts/flow-migrate-igr.js +1 -1
- package/scripts/flow-migrate.js +2 -1
- package/scripts/flow-model-adapter.js +1 -1
- package/scripts/flow-model-config.js +5 -1
- package/scripts/flow-model-profile.js +2 -1
- package/scripts/flow-orchestrate.js +3 -3
- package/scripts/flow-output.js +29 -0
- package/scripts/flow-parallel.js +10 -9
- package/scripts/flow-pattern-enforcer.js +2 -1
- package/scripts/flow-permissions-audit.js +124 -0
- package/scripts/flow-plugin-registry.js +2 -2
- package/scripts/flow-progress.js +5 -1
- package/scripts/flow-project-analyzer.js +1 -1
- package/scripts/flow-promote.js +510 -0
- package/scripts/flow-registries.js +86 -0
- package/scripts/flow-request-log.js +133 -0
- package/scripts/flow-research-protocol.js +0 -1
- package/scripts/flow-revision-tracker.js +2 -1
- package/scripts/flow-roadmap.js +2 -1
- package/scripts/flow-rules-sync.js +3 -7
- package/scripts/flow-session-end.js +3 -1
- package/scripts/flow-session-learning.js +6 -13
- package/scripts/flow-session-state.js +2 -2
- package/scripts/flow-setup-hooks.js +2 -1
- package/scripts/flow-skill-create.js +1 -1
- package/scripts/flow-skill-freshness.js +6 -7
- package/scripts/flow-skill-learn.js +1 -1
- package/scripts/flow-step-coverage.js +1 -1
- package/scripts/flow-step-security.js +1 -1
- package/scripts/flow-story.js +58 -10
- package/scripts/flow-sys.js +204 -0
- package/scripts/flow-task-hierarchy.js +88 -0
- package/scripts/flow-tech-debt.js +2 -1
- package/scripts/flow-test-api.js +1 -1
- package/scripts/flow-utils.js +60 -890
- package/scripts/hooks/core/bugfix-scope-gate.js +5 -4
- package/scripts/hooks/core/deploy-gate.js +1 -1
- package/scripts/hooks/core/pre-tool-helpers.js +72 -0
- package/scripts/hooks/core/pre-tool-orchestrator.js +442 -0
- package/scripts/hooks/core/routing-gate.js +8 -0
- package/scripts/hooks/core/session-end.js +28 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +48 -492
- package/scripts/hooks/entry/shared/hook-runner.js +1 -1
- package/scripts/registries/schema-registry.js +1 -1
- package/scripts/registries/service-registry.js +1 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Intent-Grounded Review (IGR v6.0) — Reference
|
|
2
|
+
|
|
3
|
+
Detailed reference for the IGR phases added to `/wogi-review` in v2.17.3. The main `/wogi-review` skill file contains the mandatory execution flow; this doc holds the full rationale, examples, and background each phase depends on.
|
|
4
|
+
|
|
5
|
+
Read this when:
|
|
6
|
+
- You're debugging unexpected review-skill behavior
|
|
7
|
+
- You're tuning `config.review.*` and want to understand each key's impact
|
|
8
|
+
- You're extending the review pipeline (adding a new phase, new adversary, new tier)
|
|
9
|
+
- A review surfaced a false-positive finding and you want to know which phase should have caught it
|
|
10
|
+
|
|
11
|
+
The four IGR additions to the review pipeline:
|
|
12
|
+
|
|
13
|
+
| Phase | What it adds |
|
|
14
|
+
|-------|---|
|
|
15
|
+
| Phase 0 — Framing | Scope + assumptions surfaced BEFORE agents launch |
|
|
16
|
+
| Phase 2 — Evidence Tiers | Every finding carries `evidenceTier` + `evidenceNote`; severity capped by tier |
|
|
17
|
+
| Phase 2.8 — Findings Adversary | Different-model critique of the findings themselves |
|
|
18
|
+
| Phase 5 — Completion Truth Gate | "Fixed" claims require INTERACTIVE evidence |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Phase 0: Review Framing Pass — Reference Detail
|
|
23
|
+
|
|
24
|
+
**Problem this solves**: "Review" means different things in different invocations. "Review what we just did" is bounded to the session diff; "review the auth flow" is bounded to a module; "review before ship" expects a final-sign-off posture. Without explicit framing, the AI picks its own scope and grades findings against its own mental rubric, producing a different answer than the user asked for.
|
|
25
|
+
|
|
26
|
+
### The Five Framing Fields
|
|
27
|
+
|
|
28
|
+
| Field | What it captures |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `interpretation` | One sentence: "I understand this as: review X with posture Y" |
|
|
31
|
+
| `scopeIn` | Explicit list: which files, commits, or modules are in scope |
|
|
32
|
+
| `scopeOut` | Explicit list: what this review will NOT cover (by design, not omission) |
|
|
33
|
+
| `assumptions` | 2–5 review-model assumptions (e.g., "a refactor review must verify behavior preservation, not just test pass") |
|
|
34
|
+
| `posture` | `pre-ship` / `session-review` / `security-focused` / `exploratory` — adjusts agent emphasis |
|
|
35
|
+
|
|
36
|
+
### Posture → Agent Weight Adjustment
|
|
37
|
+
|
|
38
|
+
- `pre-ship` → boost security + integration agents, require Phase 2.8 adversary pass
|
|
39
|
+
- `session-review` → balanced across all agents
|
|
40
|
+
- `security-focused` → security agent mandatory, injection/authn checks emphasized
|
|
41
|
+
- `exploratory` → logic + architecture agents; adversary pass OPTIONAL (via `config.review.framingPass.adversaryInExploratory`)
|
|
42
|
+
|
|
43
|
+
### Item Reconciliation (Anti-Deferral Guard)
|
|
44
|
+
|
|
45
|
+
When the user's request enumerated multiple focus areas ("review X, Y, Z"), each named item MUST appear in `scopeIn`. If the count shrank (user named 5, framing has 3), the framing pass FAILS — display which items were dropped and require the user to confirm before proceeding.
|
|
46
|
+
|
|
47
|
+
Ported from `/wogi-start`'s anti-deferral rule. The AI cannot silently drop items.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Phase 2: Evidence Tiers — Reference Detail
|
|
52
|
+
|
|
53
|
+
Every finding returned by any review agent MUST carry two additional fields: `evidenceTier` (0–4) and `evidenceNote` (one-line string citing what produced the evidence).
|
|
54
|
+
|
|
55
|
+
### Tier Definitions
|
|
56
|
+
|
|
57
|
+
| Tier | Name | What it means for findings |
|
|
58
|
+
|------|------|----------------------------|
|
|
59
|
+
| 0 | STATIC | AI inferred from the source alone — no grep, no execution. Weakest. |
|
|
60
|
+
| 1 | STRUCTURAL | AI grepped / globbed / counted instances across the codebase. |
|
|
61
|
+
| 2 | OBSERVATIONAL | AI ran a tool (lint, typecheck, npm audit) and read its output. |
|
|
62
|
+
| 3 | INTERACTIVE | AI executed code or tests and observed the behavior. |
|
|
63
|
+
| 4 | AUTOMATED | A quality gate or test suite produces this finding deterministically on every run. |
|
|
64
|
+
|
|
65
|
+
### Severity Cap Rules
|
|
66
|
+
|
|
67
|
+
- **Tier 0** findings: severity MUST be LOW (and flagged UNVERIFIED in the report).
|
|
68
|
+
- **Tier 1** findings: severity capped at MEDIUM unless grep returned ≥5 instances.
|
|
69
|
+
- **Tier 2+** findings: severity stands as the agent assigned.
|
|
70
|
+
|
|
71
|
+
### Why Tiers Matter (Real Incident)
|
|
72
|
+
|
|
73
|
+
During the v2.17.3 self-review (session 2026-04-15), a `code-reviewer` agent reported an F1 finding as "Critical — broken require path" without citing evidence. Manual verification via `require.resolve()` showed the path was correct — the agent's path math was flawed.
|
|
74
|
+
|
|
75
|
+
With tier enforcement, F1 would have been Tier 0 (no grep, no execution), capped at LOW, and flagged UNVERIFIED — alerting the reader to verify before acting. The evidence-tier requirement is the single most powerful rubber-stamp-prevention mechanism in the IGR toolkit.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Phase 2.8: Findings Adversary Critique — Reference Detail
|
|
80
|
+
|
|
81
|
+
This is the review analogue of the `/wogi-audit` Adversary Pass (Step 3.5) and the IGR Logic Adversary (wf-3975a001). Same pattern: different model, separate context, looking for specific defect classes.
|
|
82
|
+
|
|
83
|
+
### Adversary Model Selection Rule (CRITICAL)
|
|
84
|
+
|
|
85
|
+
The `adversaryPass.adversaryModel` is a mapping, NOT a static string. The AI resolves it at runtime by inspecting which model the review agents used.
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
"adversaryModel": {
|
|
89
|
+
"whenAgentOnSonnet": "opus",
|
|
90
|
+
"whenAgentOnOpus": "sonnet",
|
|
91
|
+
"whenAgentOnHaiku": "sonnet",
|
|
92
|
+
"default": "sonnet"
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Override-always rule**: if the resolved value equals the agent model (e.g., legacy plain-string config set to `sonnet` when agents ran on Sonnet), pick a different model instead. Same-model adversary = rubber-stamp, which defeats the entire purpose of the adversary pass.
|
|
97
|
+
|
|
98
|
+
### Specific Defect Classes to Hunt
|
|
99
|
+
|
|
100
|
+
The adversary prompt includes HUNT instructions for these patterns:
|
|
101
|
+
|
|
102
|
+
1. Findings where `evidenceTier=0` but severity ≥ HIGH
|
|
103
|
+
2. Findings that cite line numbers without quoting the surrounding code
|
|
104
|
+
3. "Broken require path" / "missing import" / "wrong type" claims without `require.resolve` / `tsc` / `grep` verification
|
|
105
|
+
4. Findings that contradict the framing's `scopeIn` / `scopeOut` declarations
|
|
106
|
+
|
|
107
|
+
### Applied Adjustments
|
|
108
|
+
|
|
109
|
+
The orchestrator applies the adversary's recommendations automatically:
|
|
110
|
+
|
|
111
|
+
- `severityAdjustments` rewrite findings' severity in the consolidated report (mark `[ADVERSARY-ADJUSTED]`)
|
|
112
|
+
- `scopeDrift` moves findings out of the main report into an "Out-of-Scope Findings" appendix (not dropped — user still sees them)
|
|
113
|
+
- `falsePositives` get marked `[DISPUTED]` in the report body (not removed — user sees both the finding AND the dispute)
|
|
114
|
+
- `missedIssues` get appended as new Tier-0 findings labeled `[ADVERSARY-FOUND]`
|
|
115
|
+
- `evidenceChallenges` downgrade the `evidenceTier` on challenged findings and re-apply the severity cap
|
|
116
|
+
|
|
117
|
+
### Verdict Semantics
|
|
118
|
+
|
|
119
|
+
- `ACCEPT` — no adjustments needed; findings are well-grounded
|
|
120
|
+
- `ACCEPT_WITH_ADJUSTMENTS` — severity caps/scope fixes applied, report still ships
|
|
121
|
+
- `REVISE_SCOPE` — framing was wrong; reviewer should restart with corrected scope
|
|
122
|
+
- `BLOCK` — adversary found a critical false positive or missed issue that makes the report untrustworthy; user must acknowledge before Phase 3 proceeds
|
|
123
|
+
|
|
124
|
+
**One pass only** — no iteration loop. If the adversary BLOCKS, the user calls it out and we re-review with adjusted scope.
|
|
125
|
+
|
|
126
|
+
### Archival
|
|
127
|
+
|
|
128
|
+
Every adversary run is archived to `.workflow/state/adversary-runs/review-{timestamp}.json` — same directory as IGR + audit adversary runs. This feeds the `flow promote` pipeline: recurring review-adversary findings graduate to `feedback-patterns.md`.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Phase 5: Completion Truth Gate — Reference Detail
|
|
133
|
+
|
|
134
|
+
**Problem this solves**: A review's "fixed" claim is only as good as the evidence behind it. A finding marked `fixed` because the AI applied an edit is NOT the same as a finding verified to work. Without a truth gate, the sign-off rubber-stamps whatever the agent says.
|
|
135
|
+
|
|
136
|
+
### Downgrade Rules
|
|
137
|
+
|
|
138
|
+
For every finding now marked `status: fixed`:
|
|
139
|
+
|
|
140
|
+
| Fix evidence tier | New status |
|
|
141
|
+
|---|---|
|
|
142
|
+
| Tier ≥ 3 (INTERACTIVE) | stays `fixed` |
|
|
143
|
+
| Tier 4 (AUTOMATED quality gate) | stays `fixed` |
|
|
144
|
+
| Tier 2 (OBSERVATIONAL — lint/typecheck pass only) | downgraded to `fixed-unverified` |
|
|
145
|
+
| Tier ≤ 1 (STATIC / STRUCTURAL) | downgraded to `implemented-unverified` |
|
|
146
|
+
|
|
147
|
+
### Persistence
|
|
148
|
+
|
|
149
|
+
Downgraded statuses are persisted to `last-review.json` — NOT silently dropped back to `fixed`. The user should consciously accept unverified fixes, not have them hidden.
|
|
150
|
+
|
|
151
|
+
### Self-Incident (v2.17.4)
|
|
152
|
+
|
|
153
|
+
In v2.17.4 I claimed to "fix all review findings." The truth gate (applied manually) caught:
|
|
154
|
+
|
|
155
|
+
- F1, F2, F3 — fixed with Tier 2+ evidence (OK)
|
|
156
|
+
- F4 — doc update only, Tier 0, should have been `implemented-unverified`
|
|
157
|
+
- M1 — deferred, but the release notes said "fix all" — promise/delivery mismatch
|
|
158
|
+
- M3 — dropped entirely, never mentioned in the commit
|
|
159
|
+
|
|
160
|
+
User correction: "You're not supposed to defer any fixes. It's up to the user to defer, not you." → Anti-Deferral Guard added to feedback-patterns + decisions.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Config Enforcement Model — Reference Detail
|
|
165
|
+
|
|
166
|
+
All `config.review.*` toggles are AI-honored, not runtime-enforced. No JavaScript reads `config.review.framingPass`, `config.review.evidenceTiers`, `config.review.adversaryPass`, or `config.review.completionTruthGate`.
|
|
167
|
+
|
|
168
|
+
The AI executing `/wogi-review` is responsible for reading these keys via `getConfig()` and honoring them. This matches `/wogi-audit`'s docs-driven model.
|
|
169
|
+
|
|
170
|
+
**Practical implication**: a user who sets `review.adversaryPass.enabled: false` will have the pass skipped ONLY if the AI respects the config. As a reviewer, always load config first and print the toggle states before launching phases.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Config Reference (all IGR keys)
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"review": {
|
|
179
|
+
"minFindings": 3,
|
|
180
|
+
"requireJustificationIfClean": true,
|
|
181
|
+
"framingPass": {
|
|
182
|
+
"enabled": true,
|
|
183
|
+
"itemReconciliation": true,
|
|
184
|
+
"adversaryInExploratory": false
|
|
185
|
+
},
|
|
186
|
+
"evidenceTiers": {
|
|
187
|
+
"enabled": true,
|
|
188
|
+
"capByTier": true
|
|
189
|
+
},
|
|
190
|
+
"adversaryPass": {
|
|
191
|
+
"enabled": true,
|
|
192
|
+
"adversaryModel": {
|
|
193
|
+
"whenAgentOnSonnet": "opus",
|
|
194
|
+
"whenAgentOnOpus": "sonnet",
|
|
195
|
+
"whenAgentOnHaiku": "sonnet",
|
|
196
|
+
"default": "sonnet"
|
|
197
|
+
},
|
|
198
|
+
"applySeverityAdjustments": true,
|
|
199
|
+
"applyScopeDrift": true,
|
|
200
|
+
"blockOnBlockVerdict": true,
|
|
201
|
+
"archiveRuns": true
|
|
202
|
+
},
|
|
203
|
+
"completionTruthGate": {
|
|
204
|
+
"enabled": true,
|
|
205
|
+
"requireInteractiveForFixed": true
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
package/.claude/settings.json
CHANGED
|
@@ -133,10 +133,43 @@
|
|
|
133
133
|
}
|
|
134
134
|
]
|
|
135
135
|
}
|
|
136
|
+
],
|
|
137
|
+
"TaskCreated": [
|
|
138
|
+
{
|
|
139
|
+
"hooks": [
|
|
140
|
+
{
|
|
141
|
+
"type": "command",
|
|
142
|
+
"command": "node scripts/hooks/entry/claude-code/task-created.js",
|
|
143
|
+
"timeout": 5
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
"PermissionDenied": [
|
|
149
|
+
{
|
|
150
|
+
"hooks": [
|
|
151
|
+
{
|
|
152
|
+
"type": "command",
|
|
153
|
+
"command": "node scripts/hooks/entry/claude-code/permission-denied.js",
|
|
154
|
+
"timeout": 5
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
"PreCompact": [
|
|
160
|
+
{
|
|
161
|
+
"hooks": [
|
|
162
|
+
{
|
|
163
|
+
"type": "command",
|
|
164
|
+
"command": "node scripts/hooks/entry/claude-code/pre-compact.js",
|
|
165
|
+
"timeout": 5
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
136
169
|
]
|
|
137
170
|
},
|
|
138
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.",
|
|
139
172
|
"_wogiFlowManaged": true,
|
|
140
|
-
"_wogiFlowVersion": "2.
|
|
173
|
+
"_wogiFlowVersion": "2.17.5",
|
|
141
174
|
"_comment": "Shared WogiFlow hook configuration. Committed to repo for team use. User-specific overrides go in settings.local.json."
|
|
142
175
|
}
|
|
@@ -35,6 +35,14 @@ Patterns that produce logic failures in practice — seen in real agent session
|
|
|
35
35
|
|
|
36
36
|
**P11.3 — Also check for EXISTING WOGIFLOW FEATURES that touch the same domain.** Before shipping any new mechanism (hook, wrapper, CLI entry, state file, config key, skill), enumerate the sibling surface: (S1) `grep -r "execSync\|spawn.*claude" lib/ scripts/`, check `.claude/commands/`, check `scripts/flow-constants.js`, check `lib/workspace.js` — does an existing feature already touch this domain? (S2) Show how the new mechanism composes, conflicts, or integrates with each sibling. "Orthogonal" is OK but must be asserted. (S3) If integration work is needed (e.g., the new wrapper needs to be injected into workspace's `execSync('claude')` call), include it in scope OR explicitly file a follow-up story. Silent omission of sibling integration = FAIL. Example violation caught live: `wogi-claude` wrapper initially missed that `lib/workspace.js:1612` spawns claude directly, so workspace-mode workers weren't restart-capable.
|
|
37
37
|
|
|
38
|
+
**P11.4 — Generative edge-case taxonomy (5 buckets, always run for any new mechanism).** Go through EACH bucket and demand a sentence of acknowledgment — "addressed by X", "N/A because Y", or "accepted limitation Z documented in spec". Blank buckets = FAIL. The buckets: **B1 Interleaving/concurrency** (TOCTOU, two instances at once, hook-in-hook races), **B2 Partial failure** (step 1 ok, step 2 fails — is the half-done state acceptable?), **B3 Boundary counts** (0x, 1x, 1000x — accumulation, caps, restart-storm), **B4 Execution-environment portability** (which OS/browser/runtime/deploy-target variants does this run in? Which are in scope vs explicitly unsupported?), **B5 Silent-failure observability** (if it breaks silently, will anyone notice? — is there a health-check surface, log line, or telemetry event?). Distinct from P11.1-P11.3 because it's GENERATIVE (force-enumeration) not REACTIVE (critique what the plan says).
|
|
39
|
+
|
|
40
|
+
**Stack-agnostic**: the buckets are universal. Substitute examples with whatever your plan's stack uses. "Windows / non-bash / filesystem" applies to CLI tools; for web frontends B4 means "Safari vs Chrome vs mobile WebView, SSR vs CSR, accessibility modes"; for mobile apps it means "iOS / Android versions, phone vs tablet"; for backends it means "Node/Python/Go runtime spread, containerized vs bare-metal, serverless cold-start". See rubric P11.4 for the full stack-specific mapping.
|
|
41
|
+
|
|
42
|
+
Cost: ~50-100 words added per plan. Value: catches architectural gaps at plan-time that otherwise surface as post-ship fires.
|
|
43
|
+
|
|
44
|
+
Reflex (stack-agnostic): *"for this mechanism — can 2 run at once? can a step half-fail? what happens at 0x and 1000x? which execution-environment variants does this run on? if this breaks silently, what surfaces it?"*
|
|
45
|
+
|
|
38
46
|
### What you are NOT looking for
|
|
39
47
|
|
|
40
48
|
- Code style, lint, naming — other gates handle these.
|
|
@@ -131,6 +131,7 @@ npm install -D wogiflow && npx flow onboard
|
|
|
131
131
|
| `/wogi-register` | Register plugins for /wogi-start routing |
|
|
132
132
|
|
|
133
133
|
See `.claude/docs/commands.md` for complete command reference.
|
|
134
|
+
See `.claude/docs/claude-code-compatibility.md` for Claude Code version features, performance tips, and env vars (incl. **`ENABLE_PROMPT_CACHING_1H=1`** recommended for API-key / Bedrock / Vertex / Foundry users).
|
|
134
135
|
|
|
135
136
|
## Natural Language Command Detection
|
|
136
137
|
|
|
@@ -243,6 +244,23 @@ If the user provides N items, ALL N must become tracked work items. No exception
|
|
|
243
244
|
|
|
244
245
|
**When genuinely unsure the work is still needed**: ask the user explicitly — "Do you still want wf-XXXX to ship this epic, or should we drop it?" Let them decide. Do NOT make that call autonomously.
|
|
245
246
|
|
|
247
|
+
### Review-Findings Anti-Deferral (MANDATORY — INCIDENT-DRIVEN)
|
|
248
|
+
|
|
249
|
+
**Extends Mid-Execution Anti-Deferral to `/wogi-review`, `/wogi-audit`, `/wogi-triage` findings.** If the user asks you to "fix all findings" / "option 1" / any variant that means "address everything," you MUST:
|
|
250
|
+
|
|
251
|
+
1. **Ship a fix for every finding that carries evidence tier ≥ 1**, regardless of effort estimate.
|
|
252
|
+
2. **Never silently convert a finding to "deferred"** in the commit or release notes without the user explicitly saying "defer X."
|
|
253
|
+
3. **If an item is genuinely too large for the current release**, STOP and ask: "Finding X requires ~Y minutes of work. Ship it in this release, split it into its own release, or defer? Your call."
|
|
254
|
+
4. **Never list a finding in the release description without fixing it.** If v2.17.4 says "fixes F1, F2, F3, M1" and M1 wasn't fixed, that's a promise/delivery mismatch — exactly the rubber-stamp pattern the Completion Truth Gate was designed to prevent.
|
|
255
|
+
|
|
256
|
+
**Anti-Rationalization Checklist for review findings** — if you catch yourself thinking any of these, STOP:
|
|
257
|
+
- "M1 is a restructure, that warrants a separate release" → WRONG. The user said fix all. Ask first if you think it's too big.
|
|
258
|
+
- "This finding is low-risk, it can wait" → WRONG. Low-risk doesn't mean drop-worthy.
|
|
259
|
+
- "The release notes will acknowledge it's deferred" → WRONG. User didn't defer. You are.
|
|
260
|
+
- "I'll mention it in the commit so it's transparent" → WRONG. Transparency ≠ permission. Ship the fix.
|
|
261
|
+
|
|
262
|
+
**Incident that promoted this rule to decisions.md**: 2026-04-15, v2.17.4 release. I claimed "fix all" in the commit message but silently deferred M1 (wogi-review.md bloat) and completely dropped M3 (_fastPath test coverage gap) from the Findings Adversary. User correction: "You're not supposed to defer any fixes. It's up to the user to defer, not you." v2.17.5 fixed M1 + M3 and added this rule.
|
|
263
|
+
|
|
246
264
|
### Task ID Format (MANDATORY)
|
|
247
265
|
|
|
248
266
|
All task IDs MUST be generated by `generateTaskId()` from `wogiflow/scripts/flow-utils.js`. **Never manually type a task ID.**
|
package/lib/installer.js
CHANGED
|
@@ -16,6 +16,7 @@ const readline = require('node:readline');
|
|
|
16
16
|
|
|
17
17
|
// Shared utilities
|
|
18
18
|
const { copyDir, safeReadJson } = require('./utils');
|
|
19
|
+
const { getTodayDate } = require('../scripts/flow-output');
|
|
19
20
|
|
|
20
21
|
// Package root (where wogi-flow is installed)
|
|
21
22
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
@@ -1029,6 +1030,27 @@ async function init(args) {
|
|
|
1029
1030
|
console.log(' /wogi-health - Check workflow health');
|
|
1030
1031
|
console.log(' /wogi-story - Create a new story');
|
|
1031
1032
|
console.log('');
|
|
1033
|
+
printPromptCachingTip();
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Print the ENABLE_PROMPT_CACHING_1H recommendation for non-subscriber Claude Code users.
|
|
1038
|
+
*
|
|
1039
|
+
* Claude Pro/Max/Team/Enterprise (OAuth via claude.ai) already get 1-hour prompt-cache TTL.
|
|
1040
|
+
* API-key, Bedrock, Vertex, and Foundry users default to 5min TTL — any pause longer than
|
|
1041
|
+
* 5min during a WogiFlow session invalidates the cached CLAUDE.md + state-file prefix and
|
|
1042
|
+
* re-pays the full input-token cost on the next turn. Enabling 1h TTL gives substantial
|
|
1043
|
+
* savings on typical multi-hour sessions.
|
|
1044
|
+
*
|
|
1045
|
+
* We surface this exactly once, during `flow init` onboarding. The full rationale lives in
|
|
1046
|
+
* `.claude/docs/claude-code-compatibility.md` under "Features in 2.1.108+".
|
|
1047
|
+
*/
|
|
1048
|
+
function printPromptCachingTip() {
|
|
1049
|
+
console.log('💡 Performance tip (API-key / Bedrock / Vertex / Foundry users):');
|
|
1050
|
+
console.log(' Set \x1b[33mENABLE_PROMPT_CACHING_1H=1\x1b[0m in your shell profile for 1-hour');
|
|
1051
|
+
console.log(' prompt-cache TTL (default is 5min). Claude subscribers already get 1h by default.');
|
|
1052
|
+
console.log(' Details: \x1b[36m.claude/docs/claude-code-compatibility.md\x1b[0m (Features in 2.1.108+)');
|
|
1053
|
+
console.log('');
|
|
1032
1054
|
}
|
|
1033
1055
|
|
|
1034
1056
|
/**
|
package/lib/utils.js
CHANGED
|
@@ -20,20 +20,46 @@ const REQUEST_TIMEOUT = 10000; // 10 seconds
|
|
|
20
20
|
const MAX_RESPONSE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Find the project root
|
|
23
|
+
* Find the project root with layered strategies:
|
|
24
|
+
* (0) WOGI_PROJECT_ROOT env var if set and valid
|
|
25
|
+
* (1) git rev-parse --show-toplevel (handles worktrees, submodules)
|
|
26
|
+
* (2) filesystem walk for .workflow
|
|
27
|
+
*
|
|
28
|
+
* Matches scripts/flow-paths.getProjectRoot semantics — enhanced 2026-04-15
|
|
29
|
+
* per audit dup-005 (wf-7072d3ac). Kept in lib/ because lib/ cannot require
|
|
30
|
+
* from scripts/ (cross-domain dependency per dual-repo-management.md).
|
|
31
|
+
*
|
|
24
32
|
* @returns {string|null} Project root path or null if not in a project
|
|
25
33
|
*/
|
|
26
34
|
function findProjectRoot() {
|
|
35
|
+
// Strategy 0: env var short-circuit
|
|
36
|
+
if (process.env.WOGI_PROJECT_ROOT && fs.existsSync(path.join(process.env.WOGI_PROJECT_ROOT, '.workflow'))) {
|
|
37
|
+
return process.env.WOGI_PROJECT_ROOT;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Strategy 1: git rev-parse (handles worktrees + submodules)
|
|
41
|
+
try {
|
|
42
|
+
const { execSync } = require('node:child_process');
|
|
43
|
+
const gitRoot = execSync('git rev-parse --show-toplevel', {
|
|
44
|
+
encoding: 'utf-8',
|
|
45
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
46
|
+
}).trim();
|
|
47
|
+
if (gitRoot && fs.existsSync(path.join(gitRoot, '.workflow'))) {
|
|
48
|
+
return gitRoot;
|
|
49
|
+
}
|
|
50
|
+
} catch (_err) {
|
|
51
|
+
// Not in git repo or git unavailable — fall through to walk
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Strategy 2: filesystem walk (works outside git)
|
|
27
55
|
let dir = process.cwd();
|
|
28
56
|
const root = path.parse(dir).root;
|
|
29
|
-
|
|
30
57
|
while (dir !== root) {
|
|
31
58
|
if (fs.existsSync(path.join(dir, '.workflow'))) {
|
|
32
59
|
return dir;
|
|
33
60
|
}
|
|
34
61
|
dir = path.dirname(dir);
|
|
35
62
|
}
|
|
36
|
-
|
|
37
63
|
return null;
|
|
38
64
|
}
|
|
39
65
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('node:fs');
|
|
13
13
|
const path = require('node:path');
|
|
14
|
+
const { safeReadJson } = require('./utils');
|
|
14
15
|
|
|
15
16
|
const { WORKSPACE_CONFIG_FILE } = require('./workspace');
|
|
16
17
|
|
|
@@ -94,7 +95,7 @@ function aggregateChangelogs(workspaceRoot, options = {}) {
|
|
|
94
95
|
|
|
95
96
|
let config;
|
|
96
97
|
try {
|
|
97
|
-
config =
|
|
98
|
+
config = safeReadJson(configPath);
|
|
98
99
|
} catch (_err) {
|
|
99
100
|
return { entries: [], memberCount: 0, totalEntries: 0 };
|
|
100
101
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
const http = require('node:http');
|
|
22
22
|
const readline = require('node:readline');
|
|
23
|
+
const { safeJsonParseContent } = require('./utils');
|
|
23
24
|
|
|
24
25
|
// ============================================================
|
|
25
26
|
// Constants
|
|
@@ -372,12 +373,9 @@ rl.on('line', (line) => {
|
|
|
372
373
|
const trimmed = line.trim();
|
|
373
374
|
if (!trimmed) return;
|
|
374
375
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
} catch (_err) {
|
|
379
|
-
// Ignore malformed JSON
|
|
380
|
-
}
|
|
376
|
+
const msg = safeJsonParseContent(trimmed);
|
|
377
|
+
if (msg) handleRequest(msg);
|
|
378
|
+
// else ignore malformed / prototype-polluting JSON
|
|
381
379
|
});
|
|
382
380
|
|
|
383
381
|
// ============================================================
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('node:fs');
|
|
13
13
|
const path = require('node:path');
|
|
14
|
+
const { safeReadJson } = require('./utils');
|
|
14
15
|
const crypto = require('node:crypto');
|
|
15
16
|
|
|
16
17
|
// ============================================================
|
|
@@ -410,7 +411,7 @@ function trackContractVersion(workspaceRoot, contractName, content, changedBy, r
|
|
|
410
411
|
let versions = { contracts: {} };
|
|
411
412
|
try {
|
|
412
413
|
if (fs.existsSync(versionsPath)) {
|
|
413
|
-
versions =
|
|
414
|
+
versions = safeReadJson(versionsPath);
|
|
414
415
|
}
|
|
415
416
|
} catch (_err) {
|
|
416
417
|
versions = { contracts: {} };
|
|
@@ -468,7 +469,7 @@ function generateContractChangelog(workspaceRoot) {
|
|
|
468
469
|
|
|
469
470
|
let versions;
|
|
470
471
|
try {
|
|
471
|
-
versions =
|
|
472
|
+
versions = safeReadJson(versionsPath);
|
|
472
473
|
} catch (_err) {
|
|
473
474
|
return '# Contract Changelog\n\nError reading contract versions file.\n';
|
|
474
475
|
}
|
|
@@ -514,7 +515,7 @@ function detectContractFormat(filePath) {
|
|
|
514
515
|
if (ext === '.ts' || ext === '.d.ts') return 'typescript';
|
|
515
516
|
if (ext === '.json') {
|
|
516
517
|
try {
|
|
517
|
-
const content =
|
|
518
|
+
const content = safeReadJson(filePath);
|
|
518
519
|
if (content.openapi || content.swagger) return 'openapi';
|
|
519
520
|
if (content.$schema?.includes('json-schema')) return 'jsonschema';
|
|
520
521
|
if (content.type || content.properties) return 'jsonschema';
|
|
@@ -693,7 +694,7 @@ function checkTypeSyncCompliance(workspaceRoot, manifest) {
|
|
|
693
694
|
// Check each consumer's schema-map for duplicates
|
|
694
695
|
const configPath = path.join(workspaceRoot, 'wogi-workspace.json');
|
|
695
696
|
try {
|
|
696
|
-
const config =
|
|
697
|
+
const config = safeReadJson(configPath);
|
|
697
698
|
|
|
698
699
|
for (const [name, memberConfig] of Object.entries(config.members || {})) {
|
|
699
700
|
const member = manifest.members?.[name];
|
package/lib/workspace-events.js
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
const fs = require('node:fs');
|
|
28
28
|
const path = require('node:path');
|
|
29
|
+
const { safeReadJson, safeJsonParseContent } = require('./utils');
|
|
29
30
|
const crypto = require('node:crypto');
|
|
30
31
|
|
|
31
32
|
// ============================================================
|
|
@@ -150,13 +151,13 @@ function readEvents(workspaceRoot, filter = {}) {
|
|
|
150
151
|
const content = fs.readFileSync(eventsPath, 'utf-8');
|
|
151
152
|
// Support both NDJSON (one JSON per line) and legacy JSON array format
|
|
152
153
|
if (content.trimStart().startsWith('[')) {
|
|
153
|
-
events =
|
|
154
|
+
events = safeJsonParseContent(content, []);
|
|
154
155
|
if (!Array.isArray(events)) events = [];
|
|
155
156
|
} else {
|
|
156
157
|
// NDJSON format
|
|
157
|
-
events = content.trim().split('\n').filter(Boolean)
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
events = content.trim().split('\n').filter(Boolean)
|
|
159
|
+
.map(line => safeJsonParseContent(line))
|
|
160
|
+
.filter(Boolean);
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
} catch (_err) {
|
|
@@ -229,7 +230,7 @@ function saveSubscription(workspaceRoot, subscription) {
|
|
|
229
230
|
|
|
230
231
|
try {
|
|
231
232
|
if (fs.existsSync(subsPath)) {
|
|
232
|
-
subs =
|
|
233
|
+
subs = safeReadJson(subsPath);
|
|
233
234
|
if (!Array.isArray(subs)) subs = [];
|
|
234
235
|
}
|
|
235
236
|
} catch (_err) {
|
|
@@ -254,7 +255,7 @@ function getActiveSubscriptions(workspaceRoot) {
|
|
|
254
255
|
|
|
255
256
|
try {
|
|
256
257
|
if (fs.existsSync(subsPath)) {
|
|
257
|
-
const subs =
|
|
258
|
+
const subs = safeReadJson(subsPath);
|
|
258
259
|
return (Array.isArray(subs) ? subs : []).filter(s => s.active);
|
|
259
260
|
}
|
|
260
261
|
} catch (_err) {
|
|
@@ -276,7 +277,7 @@ function removeSubscription(workspaceRoot, subscriptionId) {
|
|
|
276
277
|
|
|
277
278
|
try {
|
|
278
279
|
if (!fs.existsSync(subsPath)) return false;
|
|
279
|
-
let subs =
|
|
280
|
+
let subs = safeReadJson(subsPath);
|
|
280
281
|
if (!Array.isArray(subs)) return false;
|
|
281
282
|
|
|
282
283
|
const before = subs.length;
|
package/lib/workspace-gates.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
const fs = require('node:fs');
|
|
21
21
|
const path = require('node:path');
|
|
22
|
+
const { safeReadJson } = require('./utils');
|
|
22
23
|
|
|
23
24
|
const { WORKSPACE_CONFIG_FILE, WORKSPACE_DIR } = require('./workspace');
|
|
24
25
|
const { buildIntegrationMap } = require('./workspace-contracts');
|
|
@@ -150,7 +151,7 @@ function identifyCurrentMember(workspaceRoot, cwd) {
|
|
|
150
151
|
const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
|
|
151
152
|
|
|
152
153
|
try {
|
|
153
|
-
const config =
|
|
154
|
+
const config = safeReadJson(configPath);
|
|
154
155
|
for (const [name, memberConfig] of Object.entries(config.members || {})) {
|
|
155
156
|
const memberPath = path.resolve(workspaceRoot, memberConfig.path);
|
|
156
157
|
if (currentDir === memberPath || currentDir.startsWith(memberPath + path.sep)) {
|
|
@@ -187,7 +188,7 @@ function loadWorkspaceContext(workspaceRoot) {
|
|
|
187
188
|
// Load config
|
|
188
189
|
const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
|
|
189
190
|
try {
|
|
190
|
-
context.config =
|
|
191
|
+
context.config = safeReadJson(configPath);
|
|
191
192
|
} catch (_err) {
|
|
192
193
|
return context;
|
|
193
194
|
}
|
|
@@ -196,7 +197,7 @@ function loadWorkspaceContext(workspaceRoot) {
|
|
|
196
197
|
const manifestPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'workspace-manifest.json');
|
|
197
198
|
try {
|
|
198
199
|
if (fs.existsSync(manifestPath)) {
|
|
199
|
-
context.manifest =
|
|
200
|
+
context.manifest = safeReadJson(manifestPath);
|
|
200
201
|
}
|
|
201
202
|
} catch (_err) {
|
|
202
203
|
// Non-critical
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
const fs = require('node:fs');
|
|
14
14
|
const path = require('node:path');
|
|
15
|
+
const { safeReadJson } = require('./utils');
|
|
15
16
|
|
|
16
17
|
const { buildIntegrationMap } = require('./workspace-contracts');
|
|
17
18
|
|
|
@@ -190,7 +191,7 @@ function generateAllIntegrationSpecs(workspaceRoot, oldManifest, newManifest) {
|
|
|
190
191
|
try {
|
|
191
192
|
const contractPath = path.join(contractsDir, `${impact.provider}.json`);
|
|
192
193
|
if (fs.existsSync(contractPath)) {
|
|
193
|
-
contract =
|
|
194
|
+
contract = safeReadJson(contractPath);
|
|
194
195
|
}
|
|
195
196
|
} catch (_err) {
|
|
196
197
|
// No contract available — generate basic tests
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('node:fs');
|
|
13
13
|
const path = require('node:path');
|
|
14
|
+
const { safeReadJson } = require('./utils');
|
|
14
15
|
|
|
15
16
|
// ============================================================
|
|
16
17
|
// S5: Contract Drift Detection (Criterion 1)
|
|
@@ -35,7 +36,7 @@ function detectContractDrift(workspaceRoot, manifest) {
|
|
|
35
36
|
|
|
36
37
|
for (const file of files) {
|
|
37
38
|
try {
|
|
38
|
-
const spec =
|
|
39
|
+
const spec = safeReadJson(path.join(contractsDir, file));
|
|
39
40
|
if (spec.paths) {
|
|
40
41
|
for (const [routePath, methods] of Object.entries(spec.paths)) {
|
|
41
42
|
for (const method of Object.keys(methods)) {
|
|
@@ -906,7 +907,7 @@ function auditWorkspaceDimension(workspaceRoot, manifest) {
|
|
|
906
907
|
try {
|
|
907
908
|
const memberMetadata = {};
|
|
908
909
|
const configPath = path.join(workspaceRoot, 'wogi-workspace.json');
|
|
909
|
-
const config =
|
|
910
|
+
const config = safeReadJson(configPath);
|
|
910
911
|
|
|
911
912
|
for (const [name, memberConfig] of Object.entries(config.members || {})) {
|
|
912
913
|
const memberPath = path.resolve(workspaceRoot, memberConfig.path);
|
package/lib/workspace-locks.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
const fs = require('node:fs');
|
|
21
21
|
const path = require('node:path');
|
|
22
|
+
const { safeReadJson } = require('./utils');
|
|
22
23
|
const crypto = require('node:crypto');
|
|
23
24
|
|
|
24
25
|
// ============================================================
|
|
@@ -240,7 +241,7 @@ function listLocks(workspaceRoot) {
|
|
|
240
241
|
const files = fs.readdirSync(locksDir).filter(f => f.endsWith('.json'));
|
|
241
242
|
for (const file of files) {
|
|
242
243
|
try {
|
|
243
|
-
const content =
|
|
244
|
+
const content = safeReadJson(path.join(locksDir, file));
|
|
244
245
|
if (content.id && content.interface && content.owner) {
|
|
245
246
|
locks.push(content);
|
|
246
247
|
}
|