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.
Files changed (111) hide show
  1. package/.claude/commands/wogi-audit.md +212 -17
  2. package/.claude/commands/wogi-research.md +37 -0
  3. package/.claude/commands/wogi-review.md +200 -22
  4. package/.claude/commands/wogi-start.md +45 -0
  5. package/.claude/docs/claude-code-compatibility.md +46 -1
  6. package/.claude/docs/intent-grounded-review.md +209 -0
  7. package/.claude/settings.json +34 -1
  8. package/.workflow/agents/logic-adversary.md +8 -0
  9. package/.workflow/templates/claude-md.hbs +18 -0
  10. package/lib/installer.js +22 -0
  11. package/lib/utils.js +29 -3
  12. package/lib/workspace-changelog.js +2 -1
  13. package/lib/workspace-channel-server.js +4 -6
  14. package/lib/workspace-contracts.js +5 -4
  15. package/lib/workspace-events.js +8 -7
  16. package/lib/workspace-gates.js +4 -3
  17. package/lib/workspace-integration-tests.js +2 -1
  18. package/lib/workspace-intelligence.js +3 -2
  19. package/lib/workspace-locks.js +2 -1
  20. package/lib/workspace-messages.js +7 -6
  21. package/lib/workspace-routing.js +14 -26
  22. package/lib/workspace-session.js +7 -6
  23. package/lib/workspace-sync.js +9 -8
  24. package/package.json +4 -2
  25. package/scripts/base-workflow-step.js +1 -1
  26. package/scripts/flow +19 -0
  27. package/scripts/flow-adaptive-learning.js +1 -1
  28. package/scripts/flow-aggregate.js +2 -1
  29. package/scripts/flow-architect-pass.js +3 -3
  30. package/scripts/flow-archive-runs.js +372 -0
  31. package/scripts/flow-ask.js +1 -1
  32. package/scripts/flow-ast-grep.js +216 -0
  33. package/scripts/flow-audit-gates.js +1 -1
  34. package/scripts/flow-auto-learn.js +8 -11
  35. package/scripts/flow-bug.js +2 -2
  36. package/scripts/flow-capture-gate.js +644 -0
  37. package/scripts/flow-capture.js +4 -3
  38. package/scripts/flow-cli-flags.js +95 -0
  39. package/scripts/flow-community-sync.js +2 -1
  40. package/scripts/flow-community.js +6 -6
  41. package/scripts/flow-conclusion-classifier.js +310 -0
  42. package/scripts/flow-config-defaults.js +3 -3
  43. package/scripts/flow-constants.js +8 -11
  44. package/scripts/flow-context-scoring.js +1 -0
  45. package/scripts/flow-correction-detector.js +344 -3
  46. package/scripts/flow-damage-control.js +1 -1
  47. package/scripts/flow-decisions-merge.js +1 -0
  48. package/scripts/flow-done-gates.js +20 -0
  49. package/scripts/flow-done-report.js +2 -2
  50. package/scripts/flow-done.js +4 -4
  51. package/scripts/flow-epics.js +5 -11
  52. package/scripts/flow-health.js +145 -1
  53. package/scripts/flow-id.js +92 -0
  54. package/scripts/flow-io.js +15 -5
  55. package/scripts/flow-knowledge-router.js +2 -1
  56. package/scripts/flow-links.js +1 -1
  57. package/scripts/flow-log-manager.js +2 -1
  58. package/scripts/flow-logic-adversary.js +4 -4
  59. package/scripts/flow-long-input-cli.js +6 -0
  60. package/scripts/flow-long-input-stories.js +1 -1
  61. package/scripts/flow-loop-retry-learning.js +1 -1
  62. package/scripts/flow-mcp-capabilities.js +2 -3
  63. package/scripts/flow-mcp-docs.js +2 -1
  64. package/scripts/flow-memory-blocks.js +2 -1
  65. package/scripts/flow-memory-sync.js +1 -1
  66. package/scripts/flow-memory.js +767 -0
  67. package/scripts/flow-migrate-igr.js +1 -1
  68. package/scripts/flow-migrate.js +2 -1
  69. package/scripts/flow-model-adapter.js +1 -1
  70. package/scripts/flow-model-config.js +5 -1
  71. package/scripts/flow-model-profile.js +2 -1
  72. package/scripts/flow-orchestrate.js +3 -3
  73. package/scripts/flow-output.js +29 -0
  74. package/scripts/flow-parallel.js +10 -9
  75. package/scripts/flow-pattern-enforcer.js +2 -1
  76. package/scripts/flow-permissions-audit.js +124 -0
  77. package/scripts/flow-plugin-registry.js +2 -2
  78. package/scripts/flow-progress.js +5 -1
  79. package/scripts/flow-project-analyzer.js +1 -1
  80. package/scripts/flow-promote.js +510 -0
  81. package/scripts/flow-registries.js +86 -0
  82. package/scripts/flow-request-log.js +133 -0
  83. package/scripts/flow-research-protocol.js +0 -1
  84. package/scripts/flow-revision-tracker.js +2 -1
  85. package/scripts/flow-roadmap.js +2 -1
  86. package/scripts/flow-rules-sync.js +3 -7
  87. package/scripts/flow-session-end.js +3 -1
  88. package/scripts/flow-session-learning.js +6 -13
  89. package/scripts/flow-session-state.js +2 -2
  90. package/scripts/flow-setup-hooks.js +2 -1
  91. package/scripts/flow-skill-create.js +1 -1
  92. package/scripts/flow-skill-freshness.js +6 -7
  93. package/scripts/flow-skill-learn.js +1 -1
  94. package/scripts/flow-step-coverage.js +1 -1
  95. package/scripts/flow-step-security.js +1 -1
  96. package/scripts/flow-story.js +58 -10
  97. package/scripts/flow-sys.js +204 -0
  98. package/scripts/flow-task-hierarchy.js +88 -0
  99. package/scripts/flow-tech-debt.js +2 -1
  100. package/scripts/flow-test-api.js +1 -1
  101. package/scripts/flow-utils.js +60 -890
  102. package/scripts/hooks/core/bugfix-scope-gate.js +5 -4
  103. package/scripts/hooks/core/deploy-gate.js +1 -1
  104. package/scripts/hooks/core/pre-tool-helpers.js +72 -0
  105. package/scripts/hooks/core/pre-tool-orchestrator.js +442 -0
  106. package/scripts/hooks/core/routing-gate.js +8 -0
  107. package/scripts/hooks/core/session-end.js +28 -0
  108. package/scripts/hooks/entry/claude-code/pre-tool-use.js +48 -492
  109. package/scripts/hooks/entry/shared/hook-runner.js +1 -1
  110. package/scripts/registries/schema-registry.js +1 -1
  111. package/scripts/registries/service-registry.js +1 -1
@@ -10,6 +10,7 @@
10
10
 
11
11
  const fs = require('node:fs');
12
12
  const path = require('node:path');
13
+ const { safeReadJson } = require('./utils');
13
14
  const crypto = require('node:crypto');
14
15
 
15
16
  // ============================================================
@@ -122,7 +123,7 @@ function readMessages(workspaceRoot, filter = {}) {
122
123
 
123
124
  for (const file of files) {
124
125
  try {
125
- const content = JSON.parse(fs.readFileSync(path.join(messagesDir, file), 'utf-8'));
126
+ const content = safeReadJson(path.join(messagesDir, file));
126
127
  if (!content.id) continue;
127
128
 
128
129
  // Apply filters
@@ -162,7 +163,7 @@ function updateMessageStatus(workspaceRoot, messageId, newStatus, extra = {}) {
162
163
  if (!fs.existsSync(filePath)) return null;
163
164
 
164
165
  try {
165
- const message = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
166
+ const message = safeReadJson(filePath);
166
167
  message.status = newStatus;
167
168
  message.updatedAt = new Date().toISOString();
168
169
  // Safe merge: filter dangerous keys to prevent prototype pollution
@@ -260,7 +261,7 @@ function processSuggestedTask(workspaceRoot, message) {
260
261
  if (!fs.existsSync(readyPath)) return null;
261
262
 
262
263
  try {
263
- const ready = JSON.parse(fs.readFileSync(readyPath, 'utf-8'));
264
+ const ready = safeReadJson(readyPath);
264
265
  // Use generateTaskId when available, fallback to random (finding-008)
265
266
  let taskId;
266
267
  try {
@@ -395,7 +396,7 @@ function answerQuestion(workspaceRoot, originalMessageId, fromRepo, answer) {
395
396
  const filePath = path.join(workspaceRoot, '.workspace', 'messages', `${originalMessageId}.json`);
396
397
  let originalFrom = 'unknown';
397
398
  try {
398
- const original = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
399
+ const original = safeReadJson(filePath);
399
400
  originalFrom = original.from;
400
401
  } catch (_err) {
401
402
  // Non-critical
@@ -424,7 +425,7 @@ function answerQuestion(workspaceRoot, originalMessageId, fromRepo, answer) {
424
425
  function readWorkspaceConfig(workspaceRoot) {
425
426
  const configPath = path.join(workspaceRoot, 'wogi-workspace.json');
426
427
  try {
427
- return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
428
+ return safeReadJson(configPath);
428
429
  } catch (_err) {
429
430
  return null;
430
431
  }
@@ -501,7 +502,7 @@ function respondToImpactQuery(workspaceRoot, originalMessageId, fromRepo, respon
501
502
  const filePath = path.join(workspaceRoot, '.workspace', 'messages', `${originalMessageId}.json`);
502
503
  let originalFrom = 'unknown';
503
504
  try {
504
- const original = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
505
+ const original = safeReadJson(filePath);
505
506
  originalFrom = original.from;
506
507
  } catch (_err) {
507
508
  // Non-critical
@@ -10,6 +10,7 @@
10
10
 
11
11
  const fs = require('node:fs');
12
12
  const path = require('node:path');
13
+ const { safeReadJson, safeJsonParseContent } = require('./utils');
13
14
  const crypto = require('node:crypto');
14
15
  const http = require('node:http');
15
16
 
@@ -399,14 +400,8 @@ Be specific about file names, line numbers, and error messages.`,
399
400
  */
400
401
  function decomposeToRepoTasks(workspaceRoot, workspaceTask, plan) {
401
402
  const configPath = path.join(workspaceRoot, 'wogi-workspace.json');
402
- let config;
403
- try {
404
- const raw = fs.readFileSync(configPath, 'utf-8');
405
- config = JSON.parse(raw);
406
- if (!config || typeof config !== 'object') return [];
407
- } catch (_err) {
408
- return []; // Graceful fallback — config missing or malformed
409
- }
403
+ const config = safeReadJson(configPath);
404
+ if (!config || typeof config !== 'object') return [];
410
405
  const createdTasks = [];
411
406
 
412
407
  for (const step of plan.executionOrder) {
@@ -421,7 +416,7 @@ function decomposeToRepoTasks(workspaceRoot, workspaceTask, plan) {
421
416
  if (!fs.existsSync(readyPath)) continue;
422
417
 
423
418
  try {
424
- const ready = JSON.parse(fs.readFileSync(readyPath, 'utf-8'));
419
+ const ready = safeReadJson(readyPath);
425
420
  const taskId = 'wf-' + crypto.randomBytes(4).toString('hex');
426
421
 
427
422
  const repoTask = {
@@ -499,7 +494,7 @@ function updateCrossRepoBlocking(workspaceRoot, manifest) {
499
494
  const configPath = path.join(workspaceRoot, 'wogi-workspace.json');
500
495
  let config;
501
496
  try {
502
- config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
497
+ config = safeReadJson(configPath);
503
498
  } catch (_err) {
504
499
  return { blockedTasks: [], unblockedTasks: [] };
505
500
  }
@@ -514,7 +509,7 @@ function updateCrossRepoBlocking(workspaceRoot, manifest) {
514
509
  const readyPath = path.join(memberPath, '.workflow', 'state', 'ready.json');
515
510
  try {
516
511
  if (fs.existsSync(readyPath)) {
517
- const ready = JSON.parse(fs.readFileSync(readyPath, 'utf-8'));
512
+ const ready = safeReadJson(readyPath);
518
513
  memberTasks[name] = {
519
514
  path: memberPath,
520
515
  readyPath,
@@ -581,7 +576,7 @@ function formatDependencyTree(workspaceRoot, manifest) {
581
576
  const configPath = path.join(workspaceRoot, 'wogi-workspace.json');
582
577
  let config;
583
578
  try {
584
- config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
579
+ config = safeReadJson(configPath);
585
580
  } catch (_err) {
586
581
  return 'No workspace config found.';
587
582
  }
@@ -598,7 +593,7 @@ function formatDependencyTree(workspaceRoot, manifest) {
598
593
 
599
594
  try {
600
595
  if (!fs.existsSync(readyPath)) continue;
601
- const ready = JSON.parse(fs.readFileSync(readyPath, 'utf-8'));
596
+ const ready = safeReadJson(readyPath);
602
597
  const wsTasks = [...(ready.inProgress || []), ...(ready.ready || []), ...(ready.blocked || [])]
603
598
  .filter(t => t.source?.startsWith('workspace:'));
604
599
 
@@ -685,12 +680,9 @@ function checkWorkerHealth(port) {
685
680
  let body = '';
686
681
  res.on('data', chunk => { body += chunk; });
687
682
  res.on('end', () => {
688
- try {
689
- const data = JSON.parse(body);
690
- resolve({ up: data.status === 'ok', repo: data.repo || 'unknown' });
691
- } catch (_err) {
692
- resolve({ up: false, repo: 'unknown' });
693
- }
683
+ const data = safeJsonParseContent(body);
684
+ if (data) resolve({ up: data.status === 'ok', repo: data.repo || 'unknown' });
685
+ else resolve({ up: false, repo: 'unknown' });
694
686
  });
695
687
  });
696
688
 
@@ -717,13 +709,9 @@ async function dispatchToChannel(workspaceRoot, repoName, taskId, opts = {}) {
717
709
  }
718
710
 
719
711
  const configPath = path.join(workspaceRoot, 'wogi-workspace.json');
720
- let config;
721
- try {
722
- const raw = fs.readFileSync(configPath, 'utf-8');
723
- config = JSON.parse(raw);
724
- if (!config || typeof config !== 'object') throw new Error('Invalid config format');
725
- } catch (err) {
726
- return { ok: false, message: `Cannot read workspace config: ${err.message}` };
712
+ const config = safeReadJson(configPath);
713
+ if (!config || typeof config !== 'object') {
714
+ return { ok: false, message: `Cannot read workspace config at ${configPath}` };
727
715
  }
728
716
 
729
717
  const channelConfig = config.channels?.members?.[repoName];
@@ -23,6 +23,7 @@
23
23
 
24
24
  const fs = require('node:fs');
25
25
  const path = require('node:path');
26
+ const { safeReadJson } = require('./utils');
26
27
 
27
28
  const { WORKSPACE_CONFIG_FILE, WORKSPACE_DIR } = require('./workspace');
28
29
 
@@ -84,7 +85,7 @@ function saveManagerHandoff(workspaceRoot, options = {}) {
84
85
  // Read workspace config
85
86
  const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
86
87
  try {
87
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
88
+ const config = safeReadJson(configPath);
88
89
  handoff.workspaceName = config.name || '';
89
90
 
90
91
  // Read each member's task status
@@ -101,7 +102,7 @@ function saveManagerHandoff(workspaceRoot, options = {}) {
101
102
 
102
103
  try {
103
104
  if (fs.existsSync(readyPath)) {
104
- const ready = JSON.parse(fs.readFileSync(readyPath, 'utf-8'));
105
+ const ready = safeReadJson(readyPath);
105
106
  memberStatus.inProgress = (ready.inProgress || []).map(t => ({ id: t.id, title: t.title }));
106
107
  memberStatus.ready = (ready.ready || []).map(t => ({ id: t.id, title: t.title }));
107
108
  memberStatus.recentlyCompleted = (ready.recentlyCompleted || []).slice(0, 5).map(t => ({ id: t.id, title: t.title }));
@@ -120,7 +121,7 @@ function saveManagerHandoff(workspaceRoot, options = {}) {
120
121
  const wsReadyPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'ready.json');
121
122
  try {
122
123
  if (fs.existsSync(wsReadyPath)) {
123
- const wsReady = JSON.parse(fs.readFileSync(wsReadyPath, 'utf-8'));
124
+ const wsReady = safeReadJson(wsReadyPath);
124
125
  handoff.pendingTasks = (wsReady.ready || []).map(t => ({ id: t.id, title: t.title }));
125
126
  handoff.completedTasks = (wsReady.recentlyCompleted || []).slice(0, 10).map(t => ({ id: t.id, title: t.title }));
126
127
  }
@@ -135,7 +136,7 @@ function saveManagerHandoff(workspaceRoot, options = {}) {
135
136
  const files = fs.readdirSync(messagesDir).filter(f => f.endsWith('.json'));
136
137
  for (const file of files) {
137
138
  try {
138
- const msg = JSON.parse(fs.readFileSync(path.join(messagesDir, file), 'utf-8'));
139
+ const msg = safeReadJson(path.join(messagesDir, file));
139
140
  if (msg.status === 'pending') {
140
141
  handoff.unreadMessages.push({
141
142
  id: msg.id,
@@ -162,7 +163,7 @@ function saveManagerHandoff(workspaceRoot, options = {}) {
162
163
  const files = fs.readdirSync(locksDir).filter(f => f.endsWith('.json'));
163
164
  for (const file of files) {
164
165
  try {
165
- const lock = JSON.parse(fs.readFileSync(path.join(locksDir, file), 'utf-8'));
166
+ const lock = safeReadJson(path.join(locksDir, file));
166
167
  if (new Date(lock.expiresAt).getTime() > now) {
167
168
  handoff.activeLocks.push({
168
169
  interface: lock.interface,
@@ -214,7 +215,7 @@ function loadManagerHandoff(workspaceRoot) {
214
215
 
215
216
  try {
216
217
  if (fs.existsSync(handoffPath)) {
217
- return JSON.parse(fs.readFileSync(handoffPath, 'utf-8'));
218
+ return safeReadJson(handoffPath);
218
219
  }
219
220
  } catch (_err) {
220
221
  // Non-critical
@@ -9,6 +9,7 @@
9
9
 
10
10
  const fs = require('node:fs');
11
11
  const path = require('node:path');
12
+ const { safeReadJson } = require('./utils');
12
13
 
13
14
  const { readMemberMetadata, extractCapabilities, extractEndpoints, detectStack, generateManifest, WORKSPACE_CONFIG_FILE, WORKSPACE_DIR } = require('./workspace');
14
15
  const { buildIntegrationMap, detectTypeDrift } = require('./workspace-contracts');
@@ -32,7 +33,7 @@ function syncWorkspace(workspaceRoot, options = {}) {
32
33
  throw new Error('No wogi-workspace.json found. Run `flow workspace init` first.');
33
34
  }
34
35
 
35
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
36
+ const config = safeReadJson(configPath);
36
37
  const result = {
37
38
  success: true,
38
39
  membersUpdated: 0,
@@ -45,7 +46,7 @@ function syncWorkspace(workspaceRoot, options = {}) {
45
46
  let oldManifest = null;
46
47
  try {
47
48
  if (fs.existsSync(manifestPath)) {
48
- oldManifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
49
+ oldManifest = safeReadJson(manifestPath);
49
50
  }
50
51
  } catch (_err) {
51
52
  // Will regenerate from scratch
@@ -156,10 +157,10 @@ function getWorkspaceStatus(workspaceRoot) {
156
157
  const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
157
158
  if (!fs.existsSync(configPath)) return 'No workspace found. Run `flow workspace init` first.';
158
159
 
159
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
160
+ const config = safeReadJson(configPath);
160
161
  const manifestPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'workspace-manifest.json');
161
162
  const manifest = fs.existsSync(manifestPath)
162
- ? JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
163
+ ? safeReadJson(manifestPath)
163
164
  : null;
164
165
 
165
166
  const lines = [];
@@ -176,7 +177,7 @@ function getWorkspaceStatus(workspaceRoot) {
176
177
  let taskSummary = 'no .workflow/';
177
178
  try {
178
179
  if (fs.existsSync(readyPath)) {
179
- const ready = JSON.parse(fs.readFileSync(readyPath, 'utf-8'));
180
+ const ready = safeReadJson(readyPath);
180
181
  const inProgress = (ready.inProgress || []).length;
181
182
  const readyCount = (ready.ready || []).length;
182
183
  taskSummary = `${inProgress} in progress, ${readyCount} ready`;
@@ -234,7 +235,7 @@ function getWorkspaceStatus(workspaceRoot) {
234
235
  const wsReadyPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'ready.json');
235
236
  if (fs.existsSync(wsReadyPath)) {
236
237
  try {
237
- const wsReady = JSON.parse(fs.readFileSync(wsReadyPath, 'utf-8'));
238
+ const wsReady = safeReadJson(wsReadyPath);
238
239
  const wsInProgress = (wsReady.inProgress || []).length;
239
240
  const wsReadyCount = (wsReady.ready || []).length;
240
241
  if (wsInProgress > 0 || wsReadyCount > 0) {
@@ -272,7 +273,7 @@ function formatAge(date) {
272
273
  */
273
274
  function addMember(workspaceRoot, memberPath, role) {
274
275
  const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
275
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
276
+ const config = safeReadJson(configPath);
276
277
 
277
278
  const absPath = path.resolve(workspaceRoot, memberPath);
278
279
  const name = path.basename(absPath);
@@ -312,7 +313,7 @@ function addMember(workspaceRoot, memberPath, role) {
312
313
  */
313
314
  function removeMember(workspaceRoot, memberName) {
314
315
  const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
315
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
316
+ const config = safeReadJson(configPath);
316
317
 
317
318
  if (!config.members[memberName]) {
318
319
  throw new Error(`Member '${memberName}' not found in workspace.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "2.17.0",
3
+ "version": "2.18.0",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -10,8 +10,10 @@
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 && 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-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 && 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
+ "lint": "eslint scripts/ lib/ tests/",
16
+ "lint:ci": "eslint scripts/ lib/ tests/ --max-warnings 0",
15
17
  "postinstall": "node scripts/postinstall.js",
16
18
  "preuninstall": "node scripts/preuninstall.js"
17
19
  },
@@ -29,7 +29,7 @@
29
29
 
30
30
  const fs = require('node:fs');
31
31
  const path = require('node:path');
32
- const { PATHS } = require('./flow-utils');
32
+ const { PATHS, getTodayDate } = require('./flow-utils');
33
33
 
34
34
  class BaseWorkflowStep {
35
35
  /**
package/scripts/flow CHANGED
@@ -128,6 +128,16 @@ show_help() {
128
128
  echo " correct list List recent corrections"
129
129
  echo " aggregate Aggregate learnings across skills"
130
130
  echo " aggregate --promote Interactive promotion wizard"
131
+ echo " promote Scan adversary-runs + correction-patterns for promotions (writes pending-promotions.json)"
132
+ echo " promote apply Apply pending promotions to feedback-patterns.md"
133
+ echo " promote status Show pending promotions"
134
+ echo " archive Archive old adversary-runs + rotate gate-telemetry log"
135
+ echo " archive --dry-run Preview what would be archived"
136
+ echo " archive status Show what is eligible for archival"
137
+ echo " memory query ... Search state files (tasks, corrections, rules, adversary runs, request-log)"
138
+ echo " memory fetch <ref> Fetch a single ref (wf-ID, R-N, CORR-N) with related entries"
139
+ echo " memory stats Show counts across all memory surfaces"
140
+ echo " memory tag <ref> #t Annotate a ref with a tag (sidecar storage, non-mutating)"
131
141
  echo " pattern-enforce Enforce patterns from decisions/app-map"
132
142
  echo ""
133
143
  echo "Code Analysis:"
@@ -522,6 +532,15 @@ case "${1:-}" in
522
532
  aggregate)
523
533
  node "$SCRIPT_DIR/flow-aggregate.js" "${@:2}"
524
534
  ;;
535
+ promote)
536
+ node "$SCRIPT_DIR/flow-promote.js" "${@:2}"
537
+ ;;
538
+ archive)
539
+ node "$SCRIPT_DIR/flow-archive-runs.js" "${@:2}"
540
+ ;;
541
+ memory)
542
+ node "$SCRIPT_DIR/flow-memory.js" "${@:2}"
543
+ ;;
525
544
  skill-create)
526
545
  node "$SCRIPT_DIR/flow-skill-create.js" "${@:2}"
527
546
  ;;
@@ -15,7 +15,7 @@
15
15
  const fs = require('node:fs');
16
16
  const path = require('node:path');
17
17
  const { execFileSync } = require('node:child_process');
18
- const { getProjectRoot, colors, PATHS } = require('./flow-utils');
18
+ const { getProjectRoot, colors, PATHS, getTodayDate } = require('./flow-utils');
19
19
  const { error: errorMsg } = require('./flow-output');
20
20
  const { readJson } = require('./flow-io');
21
21
  const { storeSingleLearning, getAdapterPath } = require('./flow-model-adapter');
@@ -27,7 +27,8 @@ const {
27
27
  listDirs,
28
28
  color,
29
29
  success,
30
- warn
30
+ warn,
31
+ getTodayDate
31
32
  } = require('./flow-utils');
32
33
  const { syncDecisionsToRules } = require('./flow-rules-sync');
33
34
 
@@ -106,7 +106,7 @@ function isArchitectDisabled() {
106
106
  * @param {string} [opts.framing] - Framing artifact content. If absent, attempts to load via loadFramingArtifact.
107
107
  * @param {string} [opts.exploreFindings] - Consolidated explore-phase findings.
108
108
  * @param {string} [opts.scopeConfidenceAudit] - Optional per spec §2.2; degraded mode if absent.
109
- * @param {string} [opts.constitutionVersion='logic-constitution-v2']
109
+ * @param {string} [opts.constitutionVersion='logic-constitution-v3']
110
110
  * @returns {Promise<{ systemPrompt:string, userPrompt:string, metadata:Object }>}
111
111
  */
112
112
  async function buildArchitectPrompt(opts) {
@@ -114,7 +114,7 @@ async function buildArchitectPrompt(opts) {
114
114
  throw new TypeError('buildArchitectPrompt: opts.taskId required');
115
115
  }
116
116
  const taskId = opts.taskId;
117
- const constitutionVersion = opts.constitutionVersion || 'logic-constitution-v2';
117
+ const constitutionVersion = opts.constitutionVersion || 'logic-constitution-v3';
118
118
 
119
119
  const dis = isArchitectDisabled();
120
120
  if (dis.disabled) {
@@ -537,7 +537,7 @@ async function cliPrompt(argv) {
537
537
  const built = await buildArchitectPrompt({
538
538
  taskId: args.task || path.basename(inputFile, path.extname(inputFile)),
539
539
  taskInput,
540
- constitutionVersion: args.rubric || 'logic-constitution-v2',
540
+ constitutionVersion: args.rubric || 'logic-constitution-v3',
541
541
  });
542
542
  console.log('===== SYSTEM PROMPT =====');
543
543
  console.log(built.systemPrompt);