wogiflow 1.9.5 → 1.9.7
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-onboard.md +1 -1
- package/.claude/commands/wogi-research.md +70 -1
- package/.claude/commands/wogi-start.md +4 -3
- package/.claude/docs/config-reference.md +2 -2
- package/.claude/docs/knowledge-base/02-task-execution/03-verification.md +3 -3
- package/.claude/docs/knowledge-base/02-task-execution/05-session-review.md +1 -1
- package/.claude/docs/knowledge-base/02-task-execution/README.md +1 -1
- package/.claude/docs/knowledge-base/02-task-execution/trade-offs.md +1 -1
- package/.claude/docs/knowledge-base/configuration/README.md +1 -1
- package/.claude/docs/knowledge-base/configuration/all-options.md +2 -2
- package/.workflow/templates/claude-md.hbs +3 -6
- package/package.json +1 -1
- package/scripts/flow-config-defaults.js +1 -1
- package/scripts/flow-done +2 -2
- package/scripts/flow-done.js +119 -2
- package/scripts/flow-version-check.js +113 -6
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +28 -1
- package/scripts/hooks/entry/claude-code/session-start.js +11 -3
- package/.claude/rules/_internal/README.md +0 -64
- package/.claude/rules/_internal/document-structure.md +0 -77
- package/.claude/rules/_internal/dual-repo-management.md +0 -174
- package/.claude/rules/_internal/feature-refactoring-cleanup.md +0 -87
- package/.claude/rules/_internal/github-releases.md +0 -71
- package/.claude/rules/_internal/model-management.md +0 -35
- package/.claude/rules/_internal/self-maintenance.md +0 -87
- package/.claude/rules/architecture/component-reuse.md +0 -38
- package/.claude/rules/code-style/naming-conventions.md +0 -52
- package/.claude/rules/operations/git-workflows.md +0 -92
- package/.claude/rules/security/security-patterns.md +0 -176
- package/.claude/skills/figma-analyzer/knowledge/learnings.md +0 -11
- package/.workflow/specs/architecture.md.template +0 -24
- package/.workflow/specs/stack.md.template +0 -33
- package/.workflow/specs/testing.md.template +0 -36
|
@@ -701,7 +701,7 @@ Display:
|
|
|
701
701
|
for (const reqs of [featureReqs, bugfixReqs, refactorReqs]) {
|
|
702
702
|
reqs.push('requestLogEntry');
|
|
703
703
|
}
|
|
704
|
-
featureReqs.push('
|
|
704
|
+
featureReqs.push('registryUpdate');
|
|
705
705
|
|
|
706
706
|
config.qualityGates = {
|
|
707
707
|
feature: { require: featureReqs },
|
|
@@ -25,6 +25,46 @@ This command is **automatically triggered** (when strict mode is enabled) for:
|
|
|
25
25
|
5. **Integration Questions**: "How to integrate X with Y?"
|
|
26
26
|
6. **Comparison Questions**: "What can we learn from X?", "How does X compare to Y?"
|
|
27
27
|
|
|
28
|
+
## Step 0: Config Loading (MANDATORY — before all phases)
|
|
29
|
+
|
|
30
|
+
Before ANY research phase, read the project's config to determine depth, format, and verification requirements:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cat .workflow/config.json | grep -A 30 '"research"'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Extract and apply these settings:**
|
|
37
|
+
|
|
38
|
+
1. **Determine depth** (in priority order):
|
|
39
|
+
- CLI flag (`--deep`, `--quick`, etc.) → use that depth
|
|
40
|
+
- If auto-triggered (no flag): classify question type and look up `research.triggers`:
|
|
41
|
+
- Capability question ("Does X support Y?") → `triggers.capabilityQuestions` (default: `"standard"`)
|
|
42
|
+
- Feasibility question ("Is it possible to...") → `triggers.feasibilityQuestions` (default: `"deep"`)
|
|
43
|
+
- Existence question ("Is there a...") → `triggers.existenceQuestions` (default: `"standard"`)
|
|
44
|
+
- Architecture question ("How does X work?") → `triggers.architectureQuestions` (default: `"deep"`)
|
|
45
|
+
- Integration question ("How to integrate X with Y?") → `triggers.integrationQuestions` (default: `"deep"`)
|
|
46
|
+
- Comparison question ("How does X compare to Y?") → `triggers.comparisonQuestions` (default: `"deep"`)
|
|
47
|
+
- Fallback: `research.defaultDepth` (default: `"standard"`)
|
|
48
|
+
|
|
49
|
+
2. **Apply format flags** from config:
|
|
50
|
+
- `requireVerificationFormat: true` → ALL assumptions must have `[VERIFIED]`/`[UNVERIFIED]` markers
|
|
51
|
+
- `requireCitations: true` → ALL claims must have an Evidence Chain entry
|
|
52
|
+
- `assumptionTracking: true` → Assumption Stack table is MANDATORY in output
|
|
53
|
+
- `negativeEvidenceRule: true` → Negative claims require exhaustive search evidence
|
|
54
|
+
|
|
55
|
+
3. **Display header block** (MANDATORY):
|
|
56
|
+
```markdown
|
|
57
|
+
## Research Report
|
|
58
|
+
**Question:** [the question]
|
|
59
|
+
**Depth:** [resolved depth from step 1]
|
|
60
|
+
**Flow:** [Standard/Comparison]
|
|
61
|
+
**Config applied:** requireVerification=[yes/no], citations=[yes/no], assumptionTracking=[yes/no]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**If config.json has no `research` section or is unreadable**: use defaults (depth: standard, all format flags: true).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
28
68
|
## Research Protocol Phases
|
|
29
69
|
|
|
30
70
|
There are two flows depending on question type:
|
|
@@ -199,6 +239,7 @@ In `.workflow/config.json`:
|
|
|
199
239
|
"enabled": true,
|
|
200
240
|
"defaultDepth": "standard",
|
|
201
241
|
"autoTrigger": true,
|
|
242
|
+
"requireVerificationFormat": true,
|
|
202
243
|
"maxTokensPerDepth": {
|
|
203
244
|
"quick": 5000,
|
|
204
245
|
"standard": 20000,
|
|
@@ -210,7 +251,15 @@ In `.workflow/config.json`:
|
|
|
210
251
|
"cacheExpiryHours": 24,
|
|
211
252
|
"budgetMode": "soft",
|
|
212
253
|
"negativeEvidenceRule": true,
|
|
213
|
-
"assumptionTracking": true
|
|
254
|
+
"assumptionTracking": true,
|
|
255
|
+
"triggers": {
|
|
256
|
+
"capabilityQuestions": "standard",
|
|
257
|
+
"feasibilityQuestions": "deep",
|
|
258
|
+
"existenceQuestions": "standard",
|
|
259
|
+
"architectureQuestions": "deep",
|
|
260
|
+
"integrationQuestions": "deep",
|
|
261
|
+
"comparisonQuestions": "deep"
|
|
262
|
+
}
|
|
214
263
|
}
|
|
215
264
|
}
|
|
216
265
|
```
|
|
@@ -269,6 +318,26 @@ When `research.requireCitations` is enabled and `research.autoTrigger` is true:
|
|
|
269
318
|
- Claims without citations are flagged
|
|
270
319
|
- Negative claims require exhaustive search evidence
|
|
271
320
|
|
|
321
|
+
## Output Checklist (MANDATORY — self-verify before presenting)
|
|
322
|
+
|
|
323
|
+
Before presenting ANY research report, verify ALL of these are present. If any is missing, add it before outputting.
|
|
324
|
+
|
|
325
|
+
| # | Check | Required When |
|
|
326
|
+
|---|-------|---------------|
|
|
327
|
+
| 1 | **Header block** with Question, Depth, Flow, Config applied | Always |
|
|
328
|
+
| 2 | **Conclusion** section with direct answer | Always |
|
|
329
|
+
| 3 | **Assumption Stack** table with `[VERIFIED]`/`[UNVERIFIED]` markers | `assumptionTracking: true` (default) |
|
|
330
|
+
| 4 | **Evidence Chain** table with Claim, Source Type, Source Location, Confidence | `requireCitations: true` (default) |
|
|
331
|
+
| 5 | **Confidence level** (HIGH/MEDIUM/LOW) | Always |
|
|
332
|
+
| 6 | **Searches Performed** list (web + local) | Always |
|
|
333
|
+
| 7 | **Negative Evidence format** for any "X doesn't exist" claims | `negativeEvidenceRule: true` (default) |
|
|
334
|
+
| 8 | **Comparison table** (External Feature / Local Equivalent / Status) | Comparison flow only |
|
|
335
|
+
| 9 | **Recommendation Verification** (EXISTS/PARTIAL/MISSING markers) | Comparison flow only |
|
|
336
|
+
|
|
337
|
+
**Self-check prompt**: "Have I included all mandatory sections per config? Is every assumption marked? Is every claim cited?"
|
|
338
|
+
|
|
339
|
+
If the report is missing any required section, DO NOT present it — add the missing section first.
|
|
340
|
+
|
|
272
341
|
## CLI Compatibility
|
|
273
342
|
|
|
274
343
|
This command currently supports Claude Code only.
|
|
@@ -302,7 +302,7 @@ If violations found: fix, re-run, only proceed when all pass. Violations auto-re
|
|
|
302
302
|
**First**: Run `node node_modules/wogiflow/scripts/flow-spec-verifier.js verify wf-XXXXXXXX` — verify all spec deliverables exist. If missing → STOP, create them.
|
|
303
303
|
|
|
304
304
|
**Then**: Check `config.qualityGates` for task type. Gates are type-specific:
|
|
305
|
-
- **feature**: loopComplete, tests,
|
|
305
|
+
- **feature**: loopComplete, tests, registryUpdate, requestLogEntry, integrationWiring, standardsCompliance
|
|
306
306
|
- **bugfix**: loopComplete, tests, requestLogEntry, standardsCompliance, learningEnforcement
|
|
307
307
|
- **refactor**: loopComplete, tests, noNewFeatures, smokeTest, standardsCompliance
|
|
308
308
|
- **chore**: requestLogEntry, outstandingFindings
|
|
@@ -311,7 +311,8 @@ If violations found: fix, re-run, only proceed when all pass. Violations auto-re
|
|
|
311
311
|
|
|
312
312
|
**Fallback behavior**: Task types not listed above (docs, style, test, perf, etc.) inherit the **feature** gates. This is intentional — feature gates are the most comprehensive and serve as a safe default.
|
|
313
313
|
|
|
314
|
-
**Key automated gates** (v1.9.
|
|
314
|
+
**Key automated gates** (v1.9.7):
|
|
315
|
+
- `registryUpdate` → runs `flow registry-manager scan` on ALL active registries (app-map, function-map, api-map, schema-map, service-map). Auto-updates maps when new entries found. Replaces old `appMapUpdate` no-op gate.
|
|
315
316
|
- `integrationWiring` → calls `verifyWiring()` — checks created files are imported/used
|
|
316
317
|
- `standardsCompliance` → calls `runTaskStandardsCheck()` — checks naming, security, decisions.md rules
|
|
317
318
|
- `outstandingFindings` → reads `last-review.json` — blocks if unresolved critical/high findings exist
|
|
@@ -328,7 +329,7 @@ Reflection: "Have I introduced any bugs or regressions?"
|
|
|
328
329
|
1. Reflection: "Does this match what the user asked for?"
|
|
329
330
|
2. Close out all TodoWrite items for this task
|
|
330
331
|
3. Move task to recentlyCompleted in ready.json
|
|
331
|
-
4.
|
|
332
|
+
4. Registry maps auto-updated by `registryUpdate` quality gate (runs `flow registry-manager scan` on all active registries — app-map, function-map, api-map, schema-map, service-map)
|
|
332
333
|
5. If `config.webmcp.enabled` and UI files created: run `node node_modules/wogiflow/scripts/flow-webmcp-generator.js scan`
|
|
333
334
|
6. Commit: `feat: Complete wf-XXXXXXXX - [title]`
|
|
334
335
|
7. Show completion summary
|
|
@@ -163,7 +163,7 @@ Customize which checks are required per task type.
|
|
|
163
163
|
{
|
|
164
164
|
"qualityGates": {
|
|
165
165
|
"feature": {
|
|
166
|
-
"require": ["loopComplete", "tests", "
|
|
166
|
+
"require": ["loopComplete", "tests", "registryUpdate", "requestLogEntry"],
|
|
167
167
|
"optional": ["review"]
|
|
168
168
|
},
|
|
169
169
|
"bugfix": {
|
|
@@ -174,7 +174,7 @@ Customize which checks are required per task type.
|
|
|
174
174
|
}
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
**Available gates**: `loopComplete`, `tests`, `generatedTestsPass`, `uiVerification`, `apiVerification`, `
|
|
177
|
+
**Available gates**: `loopComplete`, `tests`, `generatedTestsPass`, `uiVerification`, `apiVerification`, `registryUpdate`, `requestLogEntry`, `integrationWiring`, `standardsCompliance`, `outstandingFindings`, `preRelease`, `noNewFeatures`, `smokeTest`, `learningEnforcement`, `review`, `docs`, `webmcpVerification`
|
|
178
178
|
|
|
179
179
|
**Task types**: `feature`, `bugfix`, `refactor`, `chore`, `release`, `fix`
|
|
180
180
|
|
|
@@ -62,7 +62,7 @@ Quality gates are requirements that must pass before a task can be completed.
|
|
|
62
62
|
{
|
|
63
63
|
"qualityGates": {
|
|
64
64
|
"feature": {
|
|
65
|
-
"require": ["tests", "
|
|
65
|
+
"require": ["tests", "registryUpdate", "requestLogEntry"],
|
|
66
66
|
"optional": ["review", "docs"]
|
|
67
67
|
},
|
|
68
68
|
"bugfix": {
|
|
@@ -84,7 +84,7 @@ Quality gates are requirements that must pass before a task can be completed.
|
|
|
84
84
|
| `tests` | npm test passes |
|
|
85
85
|
| `lint` | npm run lint passes (with auto-fix) |
|
|
86
86
|
| `typecheck` | npm run typecheck passes |
|
|
87
|
-
| `
|
|
87
|
+
| `registryUpdate` | All registry maps (app-map, function-map, api-map, schema-map, service-map) auto-scanned |
|
|
88
88
|
| `requestLogEntry` | Task logged in request-log.md |
|
|
89
89
|
| `noNewFeatures` | (Refactor) No new functionality added |
|
|
90
90
|
| `integrationWiring` | Created files are imported/used somewhere (not orphaned) |
|
|
@@ -134,7 +134,7 @@ Running quality gates...
|
|
|
134
134
|
✓ lint passed (auto-fixed)
|
|
135
135
|
✓ typecheck passed
|
|
136
136
|
✓ requestLogEntry (found in request-log)
|
|
137
|
-
|
|
137
|
+
✓ registryUpdate (auto-scanned: app-map.md updated)
|
|
138
138
|
|
|
139
139
|
All gates passed!
|
|
140
140
|
```
|
|
@@ -141,7 +141,7 @@ Every configuration decision in WogiFlow involves trade-offs. Understanding thes
|
|
|
141
141
|
{
|
|
142
142
|
"qualityGates": {
|
|
143
143
|
"feature": {
|
|
144
|
-
"require": ["tests", "lint", "typecheck", "
|
|
144
|
+
"require": ["tests", "lint", "typecheck", "registryUpdate", "requestLogEntry", "review"]
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
}
|
|
@@ -410,7 +410,7 @@ Per-task-type quality requirements that must pass before task completion.
|
|
|
410
410
|
"qualityGates": {
|
|
411
411
|
"preTaskBaseline": { "enabled": false },
|
|
412
412
|
"feature": {
|
|
413
|
-
"require": ["loopComplete", "tests", "
|
|
413
|
+
"require": ["loopComplete", "tests", "registryUpdate", "requestLogEntry", "integrationWiring", "standardsCompliance"],
|
|
414
414
|
"optional": ["review", "docs", "webmcpVerification"]
|
|
415
415
|
},
|
|
416
416
|
"bugfix": {
|
|
@@ -433,7 +433,7 @@ Per-task-type quality requirements that must pass before task completion.
|
|
|
433
433
|
| `qualityGates.bugfix.require` | string[] | See above | Required gates for bugfix tasks |
|
|
434
434
|
| `qualityGates.refactor.require` | string[] | See above | Required gates for refactor tasks |
|
|
435
435
|
|
|
436
|
-
Available gate values: `loopComplete`, `tests`, `
|
|
436
|
+
Available gate values: `loopComplete`, `tests`, `registryUpdate`, `requestLogEntry`, `integrationWiring`, `standardsCompliance`, `learningEnforcement`, `resolutionPopulated`, `noNewFeatures`, `smokeTest`, `review`, `docs`, `webmcpVerification`.
|
|
437
437
|
|
|
438
438
|
---
|
|
439
439
|
|
|
@@ -210,12 +210,9 @@ When creating tasks programmatically, always call `generateTaskId(title)` — ne
|
|
|
210
210
|
|
|
211
211
|
### After Completing:
|
|
212
212
|
1. Update `request-log.md` with tags
|
|
213
|
-
2.
|
|
214
|
-
3.
|
|
215
|
-
4.
|
|
216
|
-
5. Update other active registry maps if relevant — run `npx flow registry-manager scan`
|
|
217
|
-
6. Run quality gates (lint, typecheck, test)
|
|
218
|
-
7. Provide completion report
|
|
213
|
+
2. Registry maps (app-map, function-map, api-map, schema-map, service-map) are **auto-updated** by the `registryUpdate` quality gate — it runs `flow registry-manager scan` on all active registries
|
|
214
|
+
3. Run quality gates (lint, typecheck, test)
|
|
215
|
+
4. Provide completion report
|
|
219
216
|
|
|
220
217
|
## Auto-Validation (CRITICAL)
|
|
221
218
|
|
package/package.json
CHANGED
|
@@ -302,7 +302,7 @@ const CONFIG_DEFAULTS = {
|
|
|
302
302
|
qualityGates: {
|
|
303
303
|
preTaskBaseline: { enabled: false },
|
|
304
304
|
feature: {
|
|
305
|
-
require: ['loopComplete', 'tests', 'generatedTestsPass', 'uiVerification', 'apiVerification', '
|
|
305
|
+
require: ['loopComplete', 'tests', 'generatedTestsPass', 'uiVerification', 'apiVerification', 'registryUpdate', 'requestLogEntry', 'integrationWiring', 'standardsCompliance'],
|
|
306
306
|
optional: ['review', 'docs', 'webmcpVerification']
|
|
307
307
|
},
|
|
308
308
|
bugfix: {
|
package/scripts/flow-done
CHANGED
|
@@ -76,8 +76,8 @@ for gate in gates:
|
|
|
76
76
|
except:
|
|
77
77
|
print(' \033[0;33m○\033[0m requestLogEntry (verify manually)')
|
|
78
78
|
|
|
79
|
-
elif gate == 'appMapUpdate':
|
|
80
|
-
print(' \033[0;33m○\033[0m
|
|
79
|
+
elif gate == 'appMapUpdate' or gate == 'registryUpdate':
|
|
80
|
+
print(' \033[0;33m○\033[0m registryUpdate (verify manually if components created)')
|
|
81
81
|
|
|
82
82
|
else:
|
|
83
83
|
print(f' \033[0;33m○\033[0m {gate} (manual check)')
|
package/scripts/flow-done.js
CHANGED
|
@@ -121,6 +121,20 @@ try {
|
|
|
121
121
|
// v5.2 verification profiles
|
|
122
122
|
const { loadProfile: loadVerificationProfile } = require('./flow-verification-profile');
|
|
123
123
|
|
|
124
|
+
// v1.9.7 registry map update gate — lazy-loaded to avoid startup cost
|
|
125
|
+
// Loaded inside the registryUpdate gate branch only
|
|
126
|
+
let _registryManagerModule = undefined; // undefined = not yet loaded, null = load failed
|
|
127
|
+
function getRegistryManager() {
|
|
128
|
+
if (_registryManagerModule === undefined) {
|
|
129
|
+
try {
|
|
130
|
+
_registryManagerModule = require('./flow-registry-manager');
|
|
131
|
+
} catch (err) {
|
|
132
|
+
_registryManagerModule = null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return _registryManagerModule;
|
|
136
|
+
}
|
|
137
|
+
|
|
124
138
|
// Path for last failure artifact
|
|
125
139
|
const LAST_FAILURE_PATH = path.join(PATHS.state, 'last-failure.json');
|
|
126
140
|
|
|
@@ -338,8 +352,111 @@ function runQualityGates(taskId, taskType) {
|
|
|
338
352
|
if (process.env.DEBUG) console.error(`[DEBUG] requestLogEntry check: ${err.message}`);
|
|
339
353
|
console.log(` ${color('yellow', '○')} requestLogEntry (could not check)`);
|
|
340
354
|
}
|
|
341
|
-
} else if (gate === 'appMapUpdate') {
|
|
342
|
-
|
|
355
|
+
} else if (gate === 'appMapUpdate' || gate === 'registryUpdate') {
|
|
356
|
+
// v1.9.7: Programmatic registry scan — replaces manual "verify manually" no-op.
|
|
357
|
+
// Runs flow-registry-manager scan on all active registries, comparing modified files
|
|
358
|
+
// against registry entries to detect missing registrations.
|
|
359
|
+
// v1.9.8: Deprecation warning for old gate name
|
|
360
|
+
if (gate === 'appMapUpdate') {
|
|
361
|
+
console.log(` ${color('yellow', '⚠')} appMapUpdate is deprecated — update config.json qualityGates to use 'registryUpdate'`);
|
|
362
|
+
}
|
|
363
|
+
const registryMod = getRegistryManager();
|
|
364
|
+
if (registryMod) {
|
|
365
|
+
try {
|
|
366
|
+
console.log(' Running registry update check...');
|
|
367
|
+
const modifiedFiles = getModifiedFiles();
|
|
368
|
+
|
|
369
|
+
// Get map file timestamps BEFORE scan (to detect changes)
|
|
370
|
+
const mapFiles = ['app-map.md', 'function-map.md', 'api-map.md', 'schema-map.md', 'service-map.md'];
|
|
371
|
+
const beforeHashes = {};
|
|
372
|
+
for (const mf of mapFiles) {
|
|
373
|
+
const mapPath = path.join(PATHS.state, mf);
|
|
374
|
+
try {
|
|
375
|
+
beforeHashes[mf] = fs.existsSync(mapPath) ? fs.statSync(mapPath).mtimeMs : 0;
|
|
376
|
+
} catch (err) {
|
|
377
|
+
beforeHashes[mf] = 0;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Detect active plugins to check if scan is needed (lightweight, no async)
|
|
382
|
+
const { RegistryManager } = registryMod;
|
|
383
|
+
const manager = new RegistryManager();
|
|
384
|
+
manager.loadPlugins();
|
|
385
|
+
manager.detectStack();
|
|
386
|
+
manager.activatePlugins();
|
|
387
|
+
|
|
388
|
+
// Only scan if there are active plugins
|
|
389
|
+
if (manager.activePlugins.length > 0) {
|
|
390
|
+
// scanAll is async but we need sync behavior in quality gates
|
|
391
|
+
// Use spawnSync to run the scan as a child process
|
|
392
|
+
// v1.9.8: Added cwd, write JSON to stderr to avoid stdout pollution from require() side-effects
|
|
393
|
+
const scanResult = spawnSync('node', [
|
|
394
|
+
'-e',
|
|
395
|
+
`const {RegistryManager} = require(${JSON.stringify(path.join(__dirname, 'flow-registry-manager'))});
|
|
396
|
+
const m = new RegistryManager(); m.loadPlugins(); m.detectStack(); m.activatePlugins();
|
|
397
|
+
m.scanAll().then(r => { process.stderr.write('SCAN_RESULT:' + JSON.stringify(r)); process.exit(0); })
|
|
398
|
+
.catch(err => { process.stderr.write('SCAN_RESULT:' + JSON.stringify({error: err.message})); process.exit(1); });`
|
|
399
|
+
], {
|
|
400
|
+
encoding: 'utf-8',
|
|
401
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
402
|
+
timeout: 30000,
|
|
403
|
+
cwd: process.cwd()
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
if (scanResult.status === 0) {
|
|
407
|
+
// Extract scan result from stderr (after SCAN_RESULT: marker) to avoid stdout pollution
|
|
408
|
+
const stderrOutput = scanResult.stderr || '';
|
|
409
|
+
const markerIdx = stderrOutput.indexOf('SCAN_RESULT:');
|
|
410
|
+
const jsonStr = markerIdx >= 0 ? stderrOutput.slice(markerIdx + 'SCAN_RESULT:'.length) : '{}';
|
|
411
|
+
const results = safeJsonParseString(jsonStr, {});
|
|
412
|
+
|
|
413
|
+
// Check which map files were updated
|
|
414
|
+
const updatedMaps = [];
|
|
415
|
+
for (const mf of mapFiles) {
|
|
416
|
+
const mapPath = path.join(PATHS.state, mf);
|
|
417
|
+
try {
|
|
418
|
+
const afterMtime = fs.existsSync(mapPath) ? fs.statSync(mapPath).mtimeMs : 0;
|
|
419
|
+
if (afterMtime > beforeHashes[mf]) {
|
|
420
|
+
updatedMaps.push(mf);
|
|
421
|
+
}
|
|
422
|
+
} catch (err) {
|
|
423
|
+
// ignore
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Check if modified files include patterns that should be in registries
|
|
428
|
+
const relevantExtensions = ['.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte'];
|
|
429
|
+
const codeFiles = modifiedFiles.filter(f => relevantExtensions.some(ext => f.endsWith(ext)));
|
|
430
|
+
const nonTestFiles = codeFiles.filter(f => !f.includes('test') && !f.includes('spec') && !f.includes('__test'));
|
|
431
|
+
|
|
432
|
+
const activeIds = manager.activePlugins.map(p => p.constructor.id);
|
|
433
|
+
const scanSummary = Object.entries(results)
|
|
434
|
+
.filter(([id, r]) => r.success && !r.empty)
|
|
435
|
+
.map(([id]) => id);
|
|
436
|
+
|
|
437
|
+
if (updatedMaps.length > 0) {
|
|
438
|
+
console.log(` ${color('green', '✓')} registryUpdate (auto-scanned: ${updatedMaps.join(', ')} updated)`);
|
|
439
|
+
} else if (scanSummary.length > 0) {
|
|
440
|
+
console.log(` ${color('green', '✓')} registryUpdate (scanned ${activeIds.join(', ')} — maps already current)`);
|
|
441
|
+
} else if (nonTestFiles.length === 0) {
|
|
442
|
+
console.log(` ${color('green', '✓')} registryUpdate (no registrable code files modified)`);
|
|
443
|
+
} else {
|
|
444
|
+
console.log(` ${color('green', '✓')} registryUpdate (scanned — no new entries found)`);
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
console.log(` ${color('yellow', '⚠')} registryUpdate (scan error — degraded to manual check)`);
|
|
448
|
+
if (process.env.DEBUG) console.error(`[DEBUG] registry scan stderr: ${scanResult.stderr}`);
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
console.log(` ${color('green', '✓')} registryUpdate (no active registry plugins)`);
|
|
452
|
+
}
|
|
453
|
+
} catch (err) {
|
|
454
|
+
// Graceful degradation — don't block task completion on scan errors
|
|
455
|
+
console.log(` ${color('yellow', '⚠')} registryUpdate (error: ${truncateOutput(err.message, 3, 200)} — verify manually)`);
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
console.log(` ${color('yellow', '⚠')} registryUpdate (registry manager not available — verify manually)`);
|
|
459
|
+
}
|
|
343
460
|
} else if (gate === 'loopComplete') {
|
|
344
461
|
// v2.1: Explicit loop completion check
|
|
345
462
|
const activeLoop = getActiveLoop();
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* flow-version-check.js
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Called at session start
|
|
6
|
-
* to avoid nagging on every session.
|
|
4
|
+
* Version compatibility and update checks.
|
|
5
|
+
* Called at session start with two independent checks:
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* 1. Claude Code compatibility: checks once per install/update
|
|
8
|
+
* - Hard minimum (2.1.23): Hooks don't work below this — always warn
|
|
9
|
+
* - Soft gates (2.1.50+, 2.1.72+): Degrade gracefully, no warning
|
|
10
|
+
*
|
|
11
|
+
* 2. WogiFlow npm update: checks once per 24h
|
|
12
|
+
* - Fetches latest version from npm registry
|
|
13
|
+
* - Warns if local version is outdated
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
const fs = require('fs');
|
|
@@ -135,4 +139,107 @@ function checkClaudeCodeVersionOnce() {
|
|
|
135
139
|
return null;
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
|
|
142
|
+
// --- WogiFlow npm update check ---
|
|
143
|
+
|
|
144
|
+
const UPDATE_CHECK_PATH = path.join(PATHS.state, '.update-check.json');
|
|
145
|
+
const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get the installed WogiFlow version from package.json.
|
|
149
|
+
* @returns {string} Version string or 'unknown'
|
|
150
|
+
*/
|
|
151
|
+
function getLocalWogiFlowVersion() {
|
|
152
|
+
try {
|
|
153
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
154
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
155
|
+
return pkg.version || 'unknown';
|
|
156
|
+
} catch (err) {
|
|
157
|
+
return 'unknown';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Fetch the latest WogiFlow version from the npm registry.
|
|
163
|
+
* Uses a short timeout to avoid blocking session start.
|
|
164
|
+
* @returns {string|null} Latest version or null on failure
|
|
165
|
+
*/
|
|
166
|
+
function fetchLatestNpmVersion() {
|
|
167
|
+
try {
|
|
168
|
+
const output = execSync(
|
|
169
|
+
'npm view wogiflow version 2>/dev/null || echo ""',
|
|
170
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000 }
|
|
171
|
+
).trim();
|
|
172
|
+
const match = output.match(/^(\d+\.\d+\.\d+)$/);
|
|
173
|
+
return match ? match[1] : null;
|
|
174
|
+
} catch (err) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Compare two semver strings. Returns true if remote is newer than local.
|
|
181
|
+
* @param {string} local - e.g. "1.9.4"
|
|
182
|
+
* @param {string} remote - e.g. "1.9.5"
|
|
183
|
+
* @returns {boolean}
|
|
184
|
+
*/
|
|
185
|
+
function isNewerVersion(local, remote) {
|
|
186
|
+
const [lMaj, lMin, lPat] = local.split('.').map(Number);
|
|
187
|
+
const [rMaj, rMin, rPat] = remote.split('.').map(Number);
|
|
188
|
+
if (rMaj !== lMaj) return rMaj > lMaj;
|
|
189
|
+
if (rMin !== lMin) return rMin > lMin;
|
|
190
|
+
return rPat > lPat;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Check if a newer WogiFlow version is available on npm.
|
|
195
|
+
* Checks at most once per 24 hours to avoid unnecessary network calls.
|
|
196
|
+
*
|
|
197
|
+
* @returns {string|null} Warning message if outdated, null otherwise
|
|
198
|
+
*/
|
|
199
|
+
function checkWogiFlowUpdateOnce() {
|
|
200
|
+
const localVersion = getLocalWogiFlowVersion();
|
|
201
|
+
if (localVersion === 'unknown') return null;
|
|
202
|
+
|
|
203
|
+
// Check cached result
|
|
204
|
+
try {
|
|
205
|
+
const cached = fs.readFileSync(UPDATE_CHECK_PATH, 'utf-8');
|
|
206
|
+
const data = JSON.parse(cached);
|
|
207
|
+
const age = Date.now() - new Date(data.checkedAt).getTime();
|
|
208
|
+
if (age < UPDATE_CHECK_TTL_MS && data.localVersion === localVersion) {
|
|
209
|
+
// Still within TTL and same local version — return cached result
|
|
210
|
+
if (data.latestVersion && isNewerVersion(localVersion, data.latestVersion)) {
|
|
211
|
+
return `WogiFlow ${localVersion} is outdated. Latest: ${data.latestVersion}. Update with: npm install -D wogiflow@latest`;
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
} catch (err) {
|
|
216
|
+
// No cache or invalid — proceed with fresh check
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Fetch from npm
|
|
220
|
+
const latestVersion = fetchLatestNpmVersion();
|
|
221
|
+
|
|
222
|
+
// Cache the result (even if null — prevents retrying on every session)
|
|
223
|
+
try {
|
|
224
|
+
fs.mkdirSync(path.dirname(UPDATE_CHECK_PATH), { recursive: true });
|
|
225
|
+
fs.writeFileSync(UPDATE_CHECK_PATH, JSON.stringify({
|
|
226
|
+
localVersion,
|
|
227
|
+
latestVersion: latestVersion || null,
|
|
228
|
+
checkedAt: new Date().toISOString()
|
|
229
|
+
}, null, 2));
|
|
230
|
+
} catch (err) {
|
|
231
|
+
if (process.env.DEBUG) {
|
|
232
|
+
console.error(`[version-check] Failed to cache update check: ${err.message}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!latestVersion) return null;
|
|
237
|
+
|
|
238
|
+
if (isNewerVersion(localVersion, latestVersion)) {
|
|
239
|
+
return `WogiFlow ${localVersion} is outdated. Latest: ${latestVersion}. Update with: npm install -D wogiflow@latest`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = { checkClaudeCodeVersionOnce, getClaudeCodeVersion, checkWogiFlowUpdateOnce };
|
|
@@ -213,8 +213,35 @@ async function main() {
|
|
|
213
213
|
// v7.0: Subagents exempt — spawned by main agent which already went through routing.
|
|
214
214
|
// v7.1: Defense-in-depth — only bypass when an active task exists.
|
|
215
215
|
// v8.0: Added Agent, WebSearch, WebFetch to close bypass vectors.
|
|
216
|
+
// v8.1: Whitelist read-only git commands — Claude naturally runs git status/log/diff
|
|
217
|
+
// to gather context before routing. These are pure reads with no side effects.
|
|
216
218
|
const skipRoutingGateForSubagent = isSubagent && hasActiveTask();
|
|
217
|
-
|
|
219
|
+
|
|
220
|
+
// Read-only git commands whitelist — allowed before routing.
|
|
221
|
+
// These are pure read operations that cannot bypass task tracking.
|
|
222
|
+
// Safety: reject commands with shell chaining operators to prevent abuse.
|
|
223
|
+
let skipRoutingGateForReadOnlyGit = false;
|
|
224
|
+
if (toolName === 'Bash' && toolInput.command) {
|
|
225
|
+
const cmd = toolInput.command.trim();
|
|
226
|
+
const READ_ONLY_GIT_PREFIXES = [
|
|
227
|
+
'git status', 'git log', 'git diff', 'git branch',
|
|
228
|
+
'git show', 'git rev-parse', 'git remote -v', 'git tag -l',
|
|
229
|
+
'git ls-files', 'git describe'
|
|
230
|
+
];
|
|
231
|
+
// Block shell chaining operators AND control characters that could bypass prefix matching
|
|
232
|
+
const SHELL_CHAIN_OPERATORS = /[;&|`$()\n\r\\]/;
|
|
233
|
+
// Block destructive flags that could appear after an otherwise-safe prefix
|
|
234
|
+
const DESTRUCTIVE_GIT_FLAGS = /\s-[dD]\b|\s--delete\b|\s--force\b|\s--hard\b|\s--prune\b/;
|
|
235
|
+
if (
|
|
236
|
+
READ_ONLY_GIT_PREFIXES.some(prefix => cmd.startsWith(prefix)) &&
|
|
237
|
+
!SHELL_CHAIN_OPERATORS.test(cmd) &&
|
|
238
|
+
!DESTRUCTIVE_GIT_FLAGS.test(cmd)
|
|
239
|
+
) {
|
|
240
|
+
skipRoutingGateForReadOnlyGit = true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!skipRoutingGateForSubagent && !skipRoutingGateForReadOnlyGit && (toolName === 'Bash' || toolName === 'EnterPlanMode' || toolName === 'Read' || toolName === 'Glob' || toolName === 'Grep' || toolName === 'Edit' || toolName === 'Write' || toolName === 'NotebookEdit' || toolName === 'Agent' || toolName === 'WebSearch' || toolName === 'WebFetch')) {
|
|
218
245
|
try {
|
|
219
246
|
const routingResult = checkRoutingGate(toolName, config);
|
|
220
247
|
if (routingResult.blocked) {
|
|
@@ -82,12 +82,15 @@ async function main() {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
// --- Version compatibility
|
|
86
|
-
//
|
|
85
|
+
// --- Version compatibility checks ---
|
|
86
|
+
// 1. Claude Code: warns if below hard minimum (2.1.23) where hooks don't work
|
|
87
|
+
// 2. WogiFlow npm: warns if a newer version is available (checks once per 24h)
|
|
87
88
|
let versionWarning = null;
|
|
89
|
+
let updateWarning = null;
|
|
88
90
|
try {
|
|
89
|
-
const { checkClaudeCodeVersionOnce } = require('../../../flow-version-check');
|
|
91
|
+
const { checkClaudeCodeVersionOnce, checkWogiFlowUpdateOnce } = require('../../../flow-version-check');
|
|
90
92
|
versionWarning = checkClaudeCodeVersionOnce();
|
|
93
|
+
updateWarning = checkWogiFlowUpdateOnce();
|
|
91
94
|
} catch (err) {
|
|
92
95
|
if (process.env.DEBUG) {
|
|
93
96
|
console.error(`[session-start] Version check failed: ${err.message}`);
|
|
@@ -285,6 +288,11 @@ async function main() {
|
|
|
285
288
|
coreResult.context.versionWarning = versionWarning;
|
|
286
289
|
}
|
|
287
290
|
|
|
291
|
+
// Inject WogiFlow update warning (if any)
|
|
292
|
+
if (updateWarning && coreResult && coreResult.context) {
|
|
293
|
+
coreResult.context.updateWarning = updateWarning;
|
|
294
|
+
}
|
|
295
|
+
|
|
288
296
|
// Inject drift detection results (if any)
|
|
289
297
|
if (driftDetected && coreResult && coreResult.context) {
|
|
290
298
|
if (driftMarkerMissing) {
|