@claude-flow/cli 3.54.1 → 3.56.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 (116) hide show
  1. package/.claude/helpers/helpers.manifest.json +2 -2
  2. package/.claude/settings.json +9 -9
  3. package/bin/cli.js +1 -1
  4. package/bin/mcp-server.js +1 -1
  5. package/catalog-manifest.json +2 -2
  6. package/dist/src/auth/client.js +40 -24
  7. package/dist/src/commands/agent.js +12 -0
  8. package/dist/src/commands/analyze.js +18 -14
  9. package/dist/src/commands/autopilot.js +2 -2
  10. package/dist/src/commands/daemon.js +15 -3
  11. package/dist/src/commands/doctor.js +65 -58
  12. package/dist/src/commands/hive-mind.js +7 -0
  13. package/dist/src/commands/mcp.js +35 -1
  14. package/dist/src/commands/memory-backup.d.ts +1 -1
  15. package/dist/src/commands/memory-backup.js +23 -9
  16. package/dist/src/commands/memory.d.ts +0 -4
  17. package/dist/src/commands/memory.js +10 -2
  18. package/dist/src/commands/plugins.js +13 -1
  19. package/dist/src/commands/policy.js +10 -2
  20. package/dist/src/commands/task.js +12 -0
  21. package/dist/src/init/settings-generator.js +3 -3
  22. package/dist/src/mcp-server.d.ts +33 -0
  23. package/dist/src/mcp-server.js +96 -4
  24. package/dist/src/mcp-tools/agent-execute-core.js +244 -188
  25. package/dist/src/mcp-tools/agent-tools.js +15 -16
  26. package/dist/src/mcp-tools/agentdb-tools.js +22 -3
  27. package/dist/src/mcp-tools/claims-tools.js +3 -11
  28. package/dist/src/mcp-tools/hive-mind-tools.d.ts +2 -4
  29. package/dist/src/mcp-tools/hive-mind-tools.js +214 -40
  30. package/dist/src/mcp-tools/hooks-tools.js +47 -2
  31. package/dist/src/mcp-tools/memory-tools.js +29 -10
  32. package/dist/src/mcp-tools/record-store.d.ts +3 -0
  33. package/dist/src/mcp-tools/record-store.js +21 -0
  34. package/dist/src/mcp-tools/session-tools.js +47 -13
  35. package/dist/src/mcp-tools/task-tools.js +5 -11
  36. package/dist/src/mcp-tools/workflow-tools.js +78 -29
  37. package/dist/src/memory/append-conditions.d.ts +28 -0
  38. package/dist/src/memory/append-conditions.js +56 -0
  39. package/dist/src/memory/graph-edge-writer.d.ts +9 -0
  40. package/dist/src/memory/graph-edge-writer.js +90 -0
  41. package/dist/src/memory/memory-bridge.d.ts +4 -0
  42. package/dist/src/memory/memory-bridge.js +50 -9
  43. package/dist/src/memory/memory-initializer.d.ts +4 -0
  44. package/dist/src/memory/memory-initializer.js +95 -61
  45. package/dist/src/memory/memory-root.d.ts +2 -0
  46. package/dist/src/memory/memory-root.js +28 -0
  47. package/dist/src/memory/sona-optimizer.d.ts +2 -0
  48. package/dist/src/memory/sona-optimizer.js +4 -0
  49. package/dist/src/missions/schemas.d.ts +125 -125
  50. package/dist/src/mods/command-registry/schema.d.ts +199 -199
  51. package/dist/src/parser.d.ts +3 -0
  52. package/dist/src/parser.js +71 -13
  53. package/dist/src/plugins/store/discovery.d.ts +6 -0
  54. package/dist/src/plugins/store/discovery.js +7 -3
  55. package/dist/src/production/retry.js +1 -1
  56. package/dist/src/ruvector/neural-router.js +7 -2
  57. package/dist/src/services/bounded-worker-pool.js +3 -0
  58. package/dist/src/services/claim-service.js +39 -18
  59. package/dist/src/services/claims-store.d.ts +3 -0
  60. package/dist/src/services/claims-store.js +40 -0
  61. package/dist/src/services/config-file-manager.d.ts +3 -0
  62. package/dist/src/services/config-file-manager.js +24 -7
  63. package/dist/src/services/distill-tuning.js +17 -10
  64. package/dist/src/services/memory-backup.d.ts +7 -0
  65. package/dist/src/services/memory-backup.js +29 -2
  66. package/dist/src/services/memory-distillation.js +2 -2
  67. package/dist/src/services/policy-ledger-anchor.d.ts +42 -0
  68. package/dist/src/services/policy-ledger-anchor.js +163 -0
  69. package/dist/src/services/policy-runtime.d.ts +11 -5
  70. package/dist/src/services/policy-runtime.js +70 -15
  71. package/dist/src/services/worker-daemon.d.ts +5 -2
  72. package/dist/src/services/worker-daemon.js +26 -6
  73. package/dist/src/services/worker-queue.js +11 -2
  74. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  75. package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts.map +1 -1
  76. package/node_modules/@claude-flow/mcp/dist/connection-pool.js +5 -2
  77. package/node_modules/@claude-flow/mcp/dist/connection-pool.js.map +1 -1
  78. package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts +7 -1
  79. package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
  80. package/node_modules/@claude-flow/mcp/dist/transport/http.js +60 -47
  81. package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
  82. package/node_modules/@claude-flow/mcp/package.json +1 -1
  83. package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js +1 -1
  84. package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js.map +1 -1
  85. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  86. package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts +3 -0
  87. package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts.map +1 -1
  88. package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js +4 -1
  89. package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js.map +1 -1
  90. package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.d.ts.map +1 -1
  91. package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js +1 -0
  92. package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js.map +1 -1
  93. package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -1
  94. package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts +2 -1
  95. package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts.map +1 -1
  96. package/node_modules/@claude-flow/security/dist/oauth/callback-server.js +48 -16
  97. package/node_modules/@claude-flow/security/dist/oauth/callback-server.js.map +1 -1
  98. package/node_modules/@claude-flow/security/dist/path-validator.d.ts +2 -2
  99. package/node_modules/@claude-flow/security/dist/path-validator.d.ts.map +1 -1
  100. package/node_modules/@claude-flow/security/dist/path-validator.js +15 -4
  101. package/node_modules/@claude-flow/security/dist/path-validator.js.map +1 -1
  102. package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +11 -1
  103. package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
  104. package/node_modules/@claude-flow/security/dist/policy/engine.js +19 -3
  105. package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
  106. package/node_modules/@claude-flow/security/dist/policy/types.d.ts +4 -2
  107. package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
  108. package/node_modules/@claude-flow/security/package.json +1 -1
  109. package/package.json +7 -7
  110. package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -1
  111. package/plugins/ruflo-metaharness/CHANGELOG.md +48 -0
  112. package/plugins/ruflo-metaharness/scripts/_invoke.mjs +80 -7
  113. package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
  114. package/plugins/ruflo-metaharness/scripts/test-cli-resolution-3558.mjs +141 -0
  115. package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +5 -0
  116. package/.claude/agents/tmp.json +0 -0
@@ -20,6 +20,13 @@ export interface BackupResult {
20
20
  skipped?: string;
21
21
  }
22
22
  export declare function defaultMemoryDbPath(cwd?: string): string;
23
+ export interface ProjectBackupResult extends BackupResult {
24
+ backups: Array<BackupResult & {
25
+ dbPath: string;
26
+ }>;
27
+ }
28
+ /** Back up the active project stores; an explicit full-file override stays single-store. */
29
+ export declare function backupProjectMemoryDbs(cwd: string, opts?: Omit<BackupOptions, 'dbPath'>): Promise<ProjectBackupResult>;
23
30
  export declare function backupMemoryDb(opts?: BackupOptions): Promise<BackupResult>;
24
31
  export interface RestoreResult {
25
32
  restored: boolean;
@@ -15,9 +15,37 @@
15
15
  import * as fs from 'fs';
16
16
  import * as path from 'path';
17
17
  import { readFileMaybeEncrypted } from '../fs-secure.js';
18
+ import { loadBetterSqlite3 } from '../memory/shared-sqlite.js';
19
+ import { resolveMemoryRoot } from '../memory/memory-root.js';
18
20
  export function defaultMemoryDbPath(cwd = process.cwd()) {
19
21
  return path.join(cwd, '.swarm', 'memory.db');
20
22
  }
23
+ /** Back up the active project stores; an explicit full-file override stays single-store. */
24
+ export async function backupProjectMemoryDbs(cwd, opts = {}) {
25
+ const override = process.env.CLAUDE_FLOW_DB_PATH;
26
+ let sources;
27
+ if (override && override.trim().length > 0) {
28
+ sources = [path.resolve(cwd, override)];
29
+ }
30
+ else {
31
+ const root = resolveMemoryRoot(cwd);
32
+ sources = [path.join(root, 'memory.db'), path.join(root, 'agentdb-memory.db')];
33
+ }
34
+ const backups = [];
35
+ for (const dbPath of sources) {
36
+ backups.push({ ...await backupMemoryDb({ ...opts, dbPath }), dbPath });
37
+ }
38
+ const successful = backups.filter(result => result.backedUp);
39
+ const failed = backups.find(result => !result.backedUp && result.skipped !== 'no-db');
40
+ return {
41
+ ...successful[0],
42
+ backedUp: successful.length > 0 && !failed,
43
+ sizeBytes: successful.reduce((sum, result) => sum + (result.sizeBytes ?? 0), 0),
44
+ rotatedAway: successful.flatMap(result => result.rotatedAway ?? []),
45
+ skipped: failed?.skipped ?? (successful.length === 0 ? 'no-db' : undefined),
46
+ backups,
47
+ };
48
+ }
21
49
  /** ISO timestamp safe for filenames (no ':' or '.'). */
22
50
  function fileStamp(ms) {
23
51
  return new Date(ms).toISOString().replace(/[:.]/g, '-');
@@ -228,8 +256,7 @@ export async function restoreMemoryDbFromBackup(dbPath, opts = {}) {
228
256
  // encrypted snapshots and provides the same checks on WASM-only hosts.
229
257
  let Database = null;
230
258
  try {
231
- const mod = 'better-sqlite3';
232
- Database = (await import(mod)).default;
259
+ Database = await loadBetterSqlite3();
233
260
  }
234
261
  catch {
235
262
  /* use the sql.js verifier */
@@ -27,6 +27,7 @@
27
27
  import * as fs from 'fs';
28
28
  import * as path from 'path';
29
29
  import { distillTrajectoryContent, serialiseDistilled } from '../memory/structured-distill.js';
30
+ import { loadBetterSqlite3 } from '../memory/shared-sqlite.js';
30
31
  function emptyReport(dryRun, extra = {}) {
31
32
  return {
32
33
  processed: 0, episodes: 0, episodeEmbeddings: 0,
@@ -104,8 +105,7 @@ export async function runDistillation(options) {
104
105
  return emptyReport(dryRun, { skipped: 'no-db' });
105
106
  let Database;
106
107
  try {
107
- const mod = 'better-sqlite3';
108
- Database = (await import(mod)).default;
108
+ Database = await loadBetterSqlite3();
109
109
  }
110
110
  catch {
111
111
  return emptyReport(dryRun, { skipped: 'better-sqlite3 unavailable' });
@@ -0,0 +1,42 @@
1
+ import type { PolicyState } from '@claude-flow/security';
2
+ export type AnchorEvent = 'append' | 'migrated-from-state' | 'establish-anchor';
3
+ export interface AnchorEntry {
4
+ seq: number;
5
+ length: number;
6
+ headHash: string | null;
7
+ prevAnchorHash: string | null;
8
+ ts: number;
9
+ event: AnchorEvent;
10
+ by?: string;
11
+ hash: string;
12
+ }
13
+ export type AnchorAssessment = {
14
+ kind: 'absent';
15
+ } | {
16
+ kind: 'ok';
17
+ last: AnchorEntry;
18
+ behind: boolean;
19
+ } | {
20
+ kind: 'fail';
21
+ error: string;
22
+ };
23
+ export type AnchorWriter = (file: string, value: unknown) => Promise<void>;
24
+ export declare const anchorLogPath: (projectRoot: string) => string;
25
+ /**
26
+ * How the stored anchors relate to `state`; read-only. `rebuild` is the
27
+ * explicit-establish path: an unreadable log or mirror is treated as absent, but
28
+ * whatever readable anchor remains must still agree with the chain, so it can
29
+ * never bless a truncation the anchors still remember.
30
+ */
31
+ export declare function assessAnchors(projectRoot: string, state: PolicyState, rebuild?: boolean): AnchorAssessment;
32
+ /**
33
+ * Append an entry for the current state if the newest entry does not already
34
+ * describe it. Must run under the policy lock. Throws if the log is corrupt:
35
+ * only an explicit establish may replace a corrupt log.
36
+ */
37
+ export declare function recordAnchor(projectRoot: string, state: PolicyState, write: AnchorWriter, event?: AnchorEvent, by?: string): Promise<void>;
38
+ export declare function anchorPosture(projectRoot: string): {
39
+ log: boolean;
40
+ mirror: boolean;
41
+ };
42
+ //# sourceMappingURL=policy-ledger-anchor.d.ts.map
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Second, hash-chained copy of the policy ledger anchor (#3602, ADR-475).
3
+ *
4
+ * The primary anchor (`ledgerLength` / `ledgerHead`) lives inside state.json,
5
+ * the same file it protects, so deleting it together with receipts used to
6
+ * make `verify` bless the truncated chain. This module keeps an append-only
7
+ * chain of anchor entries in a SEPARATE file plus a mirror of the newest entry
8
+ * outside the project directory (~/.config/ruflo/policy-trust/<project>/).
9
+ *
10
+ * Threat model: this detects any tampering confined to state.json, and any
11
+ * tampering confined to the project directory. It does NOT stop an attacker
12
+ * who can rewrite both the project directory and the user's home directory.
13
+ */
14
+ import { createHash } from 'node:crypto';
15
+ import { existsSync, mkdirSync, readFileSync, realpathSync } from 'node:fs';
16
+ import { dirname, join, resolve } from 'node:path';
17
+ import { userInfo } from 'node:os';
18
+ const LOG_FILE = 'ledger-anchors.json';
19
+ const MIRROR_FILE = 'ledger-anchor-head.json';
20
+ export const anchorLogPath = (projectRoot) => join(resolve(projectRoot), '.claude-flow', 'policy', LOG_FILE);
21
+ function mirrorPath(projectRoot) {
22
+ const id = createHash('sha256').update(realpathSync(projectRoot)).digest('hex');
23
+ return join(userInfo().homedir, '.config', 'ruflo', 'policy-trust', id, MIRROR_FILE);
24
+ }
25
+ function entryHash(entry) {
26
+ return createHash('sha256').update(JSON.stringify([
27
+ entry.seq, entry.length, entry.headHash, entry.prevAnchorHash, entry.ts, entry.event, entry.by ?? null,
28
+ ])).digest('hex');
29
+ }
30
+ function validEntry(value, seq, prev) {
31
+ const e = value;
32
+ return !!e && typeof e === 'object'
33
+ && e.seq === seq
34
+ && Number.isInteger(e.length) && e.length >= 0
35
+ && (e.headHash === null || typeof e.headHash === 'string')
36
+ && e.prevAnchorHash === prev
37
+ && typeof e.ts === 'number'
38
+ && ['append', 'migrated-from-state', 'establish-anchor'].includes(e.event)
39
+ && typeof e.hash === 'string'
40
+ && e.hash === entryHash({ ...e, hash: undefined });
41
+ }
42
+ function readLog(projectRoot) {
43
+ const file = anchorLogPath(projectRoot);
44
+ if (!existsSync(file))
45
+ return [];
46
+ try {
47
+ const parsed = JSON.parse(readFileSync(file, 'utf8'));
48
+ if (parsed.version !== 1 || !Array.isArray(parsed.entries))
49
+ return 'corrupt';
50
+ let prev = null;
51
+ for (let i = 0; i < parsed.entries.length; i++) {
52
+ if (!validEntry(parsed.entries[i], i, prev))
53
+ return 'corrupt';
54
+ prev = parsed.entries[i].hash;
55
+ }
56
+ return parsed.entries;
57
+ }
58
+ catch {
59
+ return 'corrupt';
60
+ }
61
+ }
62
+ function readMirror(projectRoot) {
63
+ const file = mirrorPath(projectRoot);
64
+ if (!existsSync(file))
65
+ return null;
66
+ try {
67
+ const parsed = JSON.parse(readFileSync(file, 'utf8'));
68
+ return typeof parsed?.hash === 'string' && parsed.hash === entryHash({ ...parsed, hash: undefined })
69
+ ? parsed
70
+ : 'corrupt';
71
+ }
72
+ catch {
73
+ return 'corrupt';
74
+ }
75
+ }
76
+ /**
77
+ * How the stored anchors relate to `state`; read-only. `rebuild` is the
78
+ * explicit-establish path: an unreadable log or mirror is treated as absent, but
79
+ * whatever readable anchor remains must still agree with the chain, so it can
80
+ * never bless a truncation the anchors still remember.
81
+ */
82
+ export function assessAnchors(projectRoot, state, rebuild = false) {
83
+ let entries = readLog(projectRoot);
84
+ if (entries === 'corrupt') {
85
+ if (!rebuild)
86
+ return { kind: 'fail', error: 'policy-anchor-log-corrupt' };
87
+ entries = [];
88
+ }
89
+ let mirror = readMirror(projectRoot);
90
+ if (mirror === 'corrupt') {
91
+ if (!rebuild)
92
+ return { kind: 'fail', error: 'policy-anchor-mirror-corrupt' };
93
+ mirror = null;
94
+ }
95
+ if (entries.length === 0) {
96
+ if (!mirror)
97
+ return { kind: 'absent' };
98
+ if (!rebuild)
99
+ return { kind: 'fail', error: 'policy-anchor-log-missing' };
100
+ if (state.receipts.length < mirror.length)
101
+ return { kind: 'fail', error: 'policy-ledger-truncated' };
102
+ if (mirror.length > 0 && state.receipts[mirror.length - 1].hash !== mirror.headHash) {
103
+ return { kind: 'fail', error: 'policy-ledger-anchor-mismatch' };
104
+ }
105
+ return { kind: 'absent' };
106
+ }
107
+ if (mirror && !entries.some((entry) => entry.hash === mirror.hash)) {
108
+ return { kind: 'fail', error: 'policy-anchor-log-rolled-back' };
109
+ }
110
+ const last = entries[entries.length - 1];
111
+ for (const anchor of mirror ? [last, mirror] : [last]) {
112
+ if (state.receipts.length < anchor.length)
113
+ return { kind: 'fail', error: 'policy-ledger-truncated' };
114
+ if (anchor.length > 0 && state.receipts[anchor.length - 1].hash !== anchor.headHash) {
115
+ return { kind: 'fail', error: 'policy-ledger-anchor-mismatch' };
116
+ }
117
+ }
118
+ return { kind: 'ok', last, behind: state.receipts.length > last.length };
119
+ }
120
+ /**
121
+ * Append an entry for the current state if the newest entry does not already
122
+ * describe it. Must run under the policy lock. Throws if the log is corrupt:
123
+ * only an explicit establish may replace a corrupt log.
124
+ */
125
+ export async function recordAnchor(projectRoot, state, write, event = 'append', by) {
126
+ if (state.receipts.length === 0)
127
+ return;
128
+ const loaded = readLog(projectRoot);
129
+ const entries = loaded === 'corrupt' ? [] : loaded;
130
+ if (loaded === 'corrupt' && event !== 'establish-anchor')
131
+ throw new Error('policy-anchor-log-corrupt');
132
+ const last = entries[entries.length - 1];
133
+ const length = state.receipts.length;
134
+ const headHash = state.receipts[length - 1].hash;
135
+ let tip = last;
136
+ if (!last || last.length !== length || last.headHash !== headHash) {
137
+ const base = {
138
+ seq: entries.length,
139
+ length,
140
+ headHash,
141
+ prevAnchorHash: last?.hash ?? null,
142
+ ts: Date.now(),
143
+ event,
144
+ ...(by ? { by } : {}),
145
+ };
146
+ tip = { ...base, hash: entryHash(base) };
147
+ await write(anchorLogPath(projectRoot), { version: 1, entries: [...entries, tip] });
148
+ }
149
+ try {
150
+ const mirror = mirrorPath(projectRoot);
151
+ mkdirSync(dirname(mirror), { recursive: true, mode: 0o700 });
152
+ await write(mirror, tip);
153
+ }
154
+ catch (error) {
155
+ // A read-only home must not stop the ledger; verify reports the weaker
156
+ // posture (no mirror) rather than failing closed on every decision.
157
+ process.stderr.write(`[policy] anchor mirror not written: ${error.message}\n`);
158
+ }
159
+ }
160
+ export function anchorPosture(projectRoot) {
161
+ return { log: existsSync(anchorLogPath(projectRoot)), mirror: existsSync(mirrorPath(projectRoot)) };
162
+ }
163
+ //# sourceMappingURL=policy-ledger-anchor.js.map
@@ -22,13 +22,19 @@ export declare function issuePolicyApproval(approval: Omit<PolicyApproval, 'uses
22
22
  issuedAt?: number;
23
23
  }, projectRoot?: string, approvalIssuerVerifier?: (issuer: string) => boolean): Promise<PolicyApproval>;
24
24
  export declare function revokePolicyApproval(id: string, projectRoot?: string): Promise<boolean>;
25
+ export type PolicyLedgerVerification = ReturnType<AgenticPolicyEngine['verifyLedger']> & {
26
+ /** How the second anchor was brought in line during this call. */
27
+ secondaryAnchor?: 'recorded-from-state' | 'restored-primary' | 'established-explicitly';
28
+ };
25
29
  /**
26
- * Read-only verification (#3568). It must not run inside
27
- * `withPolicyTransaction`, whose own post-operation check throws a generic
28
- * `policy-ledger-verification-failed` and hides which check failed. The only
29
- * write is persisting an anchor established for a pre-anchor ledger.
30
+ * Verification (#3568, #3602). It must not run inside `withPolicyTransaction`,
31
+ * whose own post-operation check throws a generic
32
+ * `policy-ledger-verification-failed` and hides which check failed. It writes
33
+ * only to bring the second anchor in line, or on an explicit `establishAnchor`.
30
34
  */
31
- export declare function verifyPolicyLedger(projectRoot?: string): Promise<ReturnType<AgenticPolicyEngine['verifyLedger']>>;
35
+ export declare function verifyPolicyLedger(projectRoot?: string, options?: {
36
+ establishAnchor?: boolean;
37
+ }): Promise<PolicyLedgerVerification>;
32
38
  export declare function authorizeMcpTool(toolName: string, input: Record<string, unknown>, context?: Record<string, unknown>, attributes?: Readonly<{
33
39
  actionType?: string;
34
40
  network?: boolean;
@@ -5,6 +5,7 @@ import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync,
5
5
  import { dirname, join, resolve } from 'node:path';
6
6
  import { hostname, userInfo } from 'node:os';
7
7
  import { syncPolicyProjection } from '../mods/policy-projection.js';
8
+ import { assessAnchors, recordAnchor } from './policy-ledger-anchor.js';
8
9
  const POLICY_DIR = join('.claude-flow', 'policy');
9
10
  const POLICY_FILE = 'state.json';
10
11
  const LOCK_FILE = 'state.lock';
@@ -231,8 +232,11 @@ function verifyStateAnchor(projectRoot, state) {
231
232
  throw new Error('policy-state-authentication-failed');
232
233
  }
233
234
  }
234
- async function writePolicyState(projectRoot, statePath, state) {
235
+ async function writePolicyState(projectRoot, statePath, state, anchorEvent = 'append', by) {
235
236
  await writePolicyStateFiles(projectRoot, statePath, state);
237
+ // #3602: the second anchor is written after the state, so a crash leaves the
238
+ // state AHEAD of the log (caught up on the next write), never behind it.
239
+ await recordAnchor(projectRoot, state, writeJsonAtomic, anchorEvent, by);
236
240
  // ADR-404: the ruflo mod reads Claude Code tool rules from a small
237
241
  // projection, never from state.json. Written only after the state and its
238
242
  // anchor are safely down; a failure here never fails the state write. A
@@ -350,20 +354,26 @@ export async function withPolicyTransaction(projectRoot, operation, options = {}
350
354
  mkdirSync(target.dir, { recursive: true, mode: 0o700 });
351
355
  const release = await acquireLock(target.lock);
352
356
  try {
353
- const engine = AgenticPolicyEngine.fromState(loadPolicyState(projectRoot), {
357
+ const loaded = loadPolicyState(projectRoot);
358
+ const engine = AgenticPolicyEngine.fromState(loaded, {
354
359
  signingKey: process.env.CLAUDE_FLOW_POLICY_SIGNING_KEY,
355
360
  keyId: process.env.CLAUDE_FLOW_POLICY_KEY_ID,
356
361
  evidenceVerifier: verifyPolicyEvidence,
357
362
  approvalIssuerVerifier: options.approvalIssuerVerifier,
358
363
  });
364
+ // #3602: refuse to operate on a ledger whose anchors are missing or
365
+ // disagree; a restored primary anchor is persisted by the write below.
366
+ const gate = reconcileLedger(projectRoot, engine, {});
367
+ if (!gate.result.valid) {
368
+ throw new Error(gate.result.error === 'anchor-missing'
369
+ ? `policy-ledger-anchor-missing: receipts exist but no ledger anchor was found. If this ledger predates anchors, run \`ruflo policy verify --establish-anchor\`; otherwise treat it as tampering.`
370
+ : gate.result.error ?? 'policy-ledger-verification-failed');
371
+ }
359
372
  const result = await operation(engine);
360
- // Verify before exporting: verification establishes the ledger anchor
361
- // (#3568) on state written before the anchor existed, and that anchor
362
- // must be part of what is persisted.
363
373
  if (!engine.verifyLedger().valid)
364
374
  throw new Error('policy-ledger-verification-failed');
365
375
  const nextState = engine.exportState();
366
- await writePolicyState(projectRoot, target.state, nextState);
376
+ await writePolicyState(projectRoot, target.state, nextState, gate.event);
367
377
  return result;
368
378
  }
369
379
  finally {
@@ -416,12 +426,56 @@ export async function revokePolicyApproval(id, projectRoot = process.cwd()) {
416
426
  return withPolicyTransaction(projectRoot, (engine) => engine.revokeApproval(id));
417
427
  }
418
428
  /**
419
- * Read-only verification (#3568). It must not run inside
420
- * `withPolicyTransaction`, whose own post-operation check throws a generic
421
- * `policy-ledger-verification-failed` and hides which check failed. The only
422
- * write is persisting an anchor established for a pre-anchor ledger.
429
+ * Decide whether the ledger may be trusted, using the chain, the primary anchor
430
+ * in state.json and the second anchor (#3602). It never creates an anchor over
431
+ * existing receipts on its own: only an authentic second anchor (restoring a
432
+ * deleted primary) or an explicit `establishAnchor` does.
433
+ */
434
+ function reconcileLedger(projectRoot, engine, options) {
435
+ const state = engine.exportState();
436
+ const length = state.receipts.length;
437
+ const primary = typeof state.ledgerLength === 'number';
438
+ const base = engine.verifyLedger();
439
+ const fail = (error) => ({ result: { valid: false, length, error }, persist: false });
440
+ if (!base.valid && base.error !== 'anchor-missing')
441
+ return { result: base, persist: false };
442
+ const assessed = assessAnchors(projectRoot, state, options.establishAnchor === true);
443
+ if (assessed.kind === 'fail')
444
+ return fail(assessed.error);
445
+ if (primary) {
446
+ if (assessed.kind === 'absent' && length > 0) {
447
+ return options.establishAnchor
448
+ ? { result: { ...base, secondaryAnchor: 'established-explicitly' }, persist: true, event: 'establish-anchor' }
449
+ : { result: { ...base, secondaryAnchor: 'recorded-from-state' }, persist: true, event: 'migrated-from-state' };
450
+ }
451
+ return { result: base, persist: assessed.kind === 'ok' && assessed.behind, event: 'append' };
452
+ }
453
+ // No primary anchor.
454
+ if (length === 0)
455
+ return { result: { valid: true, length, state: 'empty' }, persist: false };
456
+ if (assessed.kind === 'ok') {
457
+ // The second anchor is authentic and agrees with the chain: the deleted
458
+ // primary can be restored from it. Truncation was already rejected above.
459
+ engine.verifyLedger({ establishAnchor: true });
460
+ return { result: { valid: true, length, secondaryAnchor: 'restored-primary' }, persist: true, event: 'append' };
461
+ }
462
+ if (options.establishAnchor) {
463
+ const established = engine.verifyLedger({ establishAnchor: true });
464
+ return {
465
+ result: { ...established, secondaryAnchor: 'established-explicitly' },
466
+ persist: true,
467
+ event: 'establish-anchor',
468
+ };
469
+ }
470
+ return fail('anchor-missing');
471
+ }
472
+ /**
473
+ * Verification (#3568, #3602). It must not run inside `withPolicyTransaction`,
474
+ * whose own post-operation check throws a generic
475
+ * `policy-ledger-verification-failed` and hides which check failed. It writes
476
+ * only to bring the second anchor in line, or on an explicit `establishAnchor`.
423
477
  */
424
- export async function verifyPolicyLedger(projectRoot = process.cwd()) {
478
+ export async function verifyPolicyLedger(projectRoot = process.cwd(), options = {}) {
425
479
  const target = paths(projectRoot);
426
480
  mkdirSync(target.dir, { recursive: true, mode: 0o700 });
427
481
  const release = await acquireLock(target.lock);
@@ -430,11 +484,12 @@ export async function verifyPolicyLedger(projectRoot = process.cwd()) {
430
484
  signingKey: process.env.CLAUDE_FLOW_POLICY_SIGNING_KEY,
431
485
  keyId: process.env.CLAUDE_FLOW_POLICY_KEY_ID,
432
486
  });
433
- const result = engine.verifyLedger();
434
- if (result.anchor === 'established-now') {
435
- await writePolicyState(projectRoot, target.state, engine.exportState());
487
+ const outcome = reconcileLedger(projectRoot, engine, options);
488
+ if (outcome.persist) {
489
+ const by = outcome.event === 'establish-anchor' ? userInfo().username : undefined;
490
+ await writePolicyState(projectRoot, target.state, engine.exportState(), outcome.event, by);
436
491
  }
437
- return result;
492
+ return outcome.result;
438
493
  }
439
494
  finally {
440
495
  release();
@@ -67,7 +67,10 @@ export interface DaemonConfig {
67
67
  idleShutdownMs: number;
68
68
  aiWorkersEnabled: boolean;
69
69
  workers: WorkerConfig[];
70
+ enabledWorkers?: string[];
70
71
  }
72
+ /** Parse only worker types the daemon can actually schedule. */
73
+ export declare function parseEnabledWorkers(value: string): WorkerType[];
71
74
  /**
72
75
  * Worker Daemon - Manages background workers with Node.js
73
76
  */
@@ -367,8 +370,8 @@ export declare class WorkerDaemon extends EventEmitter {
367
370
  */
368
371
  private runConsolidateWorker;
369
372
  /**
370
- * Nightly memory-DB backup worker (24h interval). Takes a WAL-safe, consistent
371
- * snapshot of .swarm/memory.db with rotation (keep last N). Never throws — a
373
+ * Nightly memory-DB backup worker (24h interval). Takes WAL-safe, consistent
374
+ * snapshots of the active CLI and AgentDB stores with rotation (keep last N). Never throws — a
372
375
  * worker must not crash the daemon; a skip/error is written to the metrics
373
376
  * file. Opt-out by omitting `backup` from `-w`; offsite GCS is opt-in via
374
377
  * RUFLO_BACKUP_GCS (a gs:// prefix), retention via RUFLO_BACKUP_KEEP.
@@ -24,7 +24,7 @@ import { HeadlessWorkerExecutor, isHeadlessWorker, } from './headless-worker-exe
24
24
  // incremental (rowid cursor), non-destructive, transactional, and
25
25
  // quick_check-gated, so it's safe to call unconditionally on every tick.
26
26
  import { runDistillation, defaultMemoryDbPath } from './memory-distillation.js';
27
- import { backupMemoryDb } from './memory-backup.js';
27
+ import { backupProjectMemoryDbs } from './memory-backup.js';
28
28
  import { resolveGitWorkspaceIdentity } from './git-workspace-identity.js';
29
29
  import { getWorkspaceLeaseRegistry } from './workspace-lease.js';
30
30
  import { getRepoSupervisorRegistry } from './repo-supervisor.js';
@@ -43,6 +43,14 @@ const DEFAULT_WORKERS = [
43
43
  { type: 'predict', intervalMs: 10 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Predictive preloading', enabled: false },
44
44
  { type: 'document', intervalMs: 60 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Auto-documentation', enabled: false },
45
45
  ];
46
+ /** Parse only worker types the daemon can actually schedule. */
47
+ export function parseEnabledWorkers(value) {
48
+ const names = value.split(',');
49
+ if (!names.length || names.some(name => !DEFAULT_WORKERS.some(worker => worker.type === name))) {
50
+ throw new Error(`--workers must be a comma-separated list of: ${DEFAULT_WORKERS.map(worker => worker.type).join(',')}`);
51
+ }
52
+ return [...new Set(names)];
53
+ }
46
54
  // Worker timeout — must exceed the longest per-worker headless timeout (15 min for audit/refactor).
47
55
  // Previously 5 min, which caused orphan processes when daemon timeout fired before executor timeout (#1117).
48
56
  const DEFAULT_WORKER_TIMEOUT_MS = 16 * 60 * 1000;
@@ -192,7 +200,8 @@ export class WorkerDaemon extends EventEmitter {
192
200
  aiWorkersEnabled: config?.aiWorkersEnabled
193
201
  ?? fileConfig.aiWorkersEnabled
194
202
  ?? (process.env.RUFLO_DAEMON_AI_WORKERS === '1'),
195
- workers: config?.workers ?? DEFAULT_WORKERS,
203
+ workers: (config?.workers ?? DEFAULT_WORKERS).map(worker => ({ ...worker })),
204
+ enabledWorkers: config?.enabledWorkers,
196
205
  };
197
206
  // #2935 — deferred from readDaemonConfigFromFile(): this.config (and
198
207
  // therefore this.config.logDir) doesn't exist until the assignment
@@ -700,6 +709,17 @@ export class WorkerDaemon extends EventEmitter {
700
709
  // Ignore parse errors, start fresh
701
710
  }
702
711
  }
712
+ // #3547 (#1968 follow-up): an explicit `--workers` selection wins over
713
+ // both DEFAULT_WORKERS and whatever daemon-state.json just restored
714
+ // above — otherwise `daemon start --workers map,audit` always ran the
715
+ // full default set (or a stale saved selection) because nothing ever
716
+ // applied the requested list to `enabled`.
717
+ if (this.config.enabledWorkers) {
718
+ const selected = new Set(this.config.enabledWorkers);
719
+ for (const workerConfig of this.config.workers) {
720
+ workerConfig.enabled = selected.has(workerConfig.type);
721
+ }
722
+ }
703
723
  // Initialize any missing workers
704
724
  for (const workerConfig of this.config.workers) {
705
725
  if (!this.workers.has(workerConfig.type)) {
@@ -1615,8 +1635,8 @@ export class WorkerDaemon extends EventEmitter {
1615
1635
  return result;
1616
1636
  }
1617
1637
  /**
1618
- * Nightly memory-DB backup worker (24h interval). Takes a WAL-safe, consistent
1619
- * snapshot of .swarm/memory.db with rotation (keep last N). Never throws — a
1638
+ * Nightly memory-DB backup worker (24h interval). Takes WAL-safe, consistent
1639
+ * snapshots of the active CLI and AgentDB stores with rotation (keep last N). Never throws — a
1620
1640
  * worker must not crash the daemon; a skip/error is written to the metrics
1621
1641
  * file. Opt-out by omitting `backup` from `-w`; offsite GCS is opt-in via
1622
1642
  * RUFLO_BACKUP_GCS (a gs:// prefix), retention via RUFLO_BACKUP_KEEP.
@@ -1629,14 +1649,14 @@ export class WorkerDaemon extends EventEmitter {
1629
1649
  let result;
1630
1650
  try {
1631
1651
  const keepEnv = Number(process.env.RUFLO_BACKUP_KEEP);
1632
- const r = await backupMemoryDb({
1633
- dbPath: defaultMemoryDbPath(this.projectRoot),
1652
+ const r = await backupProjectMemoryDbs(this.projectRoot, {
1634
1653
  keep: Number.isFinite(keepEnv) && keepEnv > 0 ? keepEnv : 7,
1635
1654
  gcs: process.env.RUFLO_BACKUP_GCS || undefined,
1636
1655
  });
1637
1656
  result = {
1638
1657
  timestamp: new Date().toISOString(),
1639
1658
  backedUp: r.backedUp,
1659
+ backups: r.backups,
1640
1660
  path: r.path,
1641
1661
  sizeBytes: r.sizeBytes ?? 0,
1642
1662
  rotatedAway: r.rotatedAway?.length ?? 0,
@@ -233,8 +233,9 @@ export class WorkerQueue extends EventEmitter {
233
233
  // Check queues in priority order
234
234
  for (const workerType of workerTypes) {
235
235
  const queueName = this.getQueueName(workerType);
236
- const taskId = this.store.popFromQueue(queueName);
237
- if (taskId) {
236
+ let taskId;
237
+ // Cancelled or otherwise stale entries do not exhaust this worker type.
238
+ while ((taskId = this.store.popFromQueue(queueName)) !== null) {
238
239
  const task = this.store.getTask(taskId);
239
240
  if (task && task.status === 'pending') {
240
241
  task.status = 'processing';
@@ -258,6 +259,10 @@ export class WorkerQueue extends EventEmitter {
258
259
  this.emit('warning', { message: `Task ${taskId} not found for completion` });
259
260
  return;
260
261
  }
262
+ // Late or duplicate outcomes must not rewrite a settled task.
263
+ if (['completed', 'failed', 'timeout', 'cancelled'].includes(task.status)) {
264
+ return;
265
+ }
261
266
  task.status = 'completed';
262
267
  task.completedAt = new Date();
263
268
  task.result = result;
@@ -276,6 +281,10 @@ export class WorkerQueue extends EventEmitter {
276
281
  this.emit('warning', { message: `Task ${taskId} not found for failure` });
277
282
  return;
278
283
  }
284
+ // Late or duplicate outcomes must not rewrite a settled task.
285
+ if (['completed', 'failed', 'timeout', 'cancelled'].includes(task.status)) {
286
+ return;
287
+ }
279
288
  this.processingTasks.delete(taskId);
280
289
  // Check if we should retry
281
290
  if (retryable && task.retryCount < task.maxRetries) {
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"connection-pool.d.ts","sourceRoot":"","sources":["../src/connection-pool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EAEpB,eAAe,EACf,OAAO,EACP,aAAa,EACd,MAAM,YAAY,CAAC;AAoDpB,qBAAa,cAAe,SAAQ,YAAa,YAAW,eAAe;IAmBvE,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAnBhC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6C;IACzE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAuB;IACtD,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,cAAc,CAAkB;IAExC,OAAO,CAAC,KAAK,CAOX;gBAGA,MAAM,EAAE,OAAO,CAAC,oBAAoB,CAAC,YAAK,EACzB,MAAM,EAAE,OAAO,EACf,aAAa,GAAE,aAA4B;YAQhD,wBAAwB;YAWxB,gBAAgB;IAaxB,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAiC1C,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI;IAuB3C,OAAO,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI;IAoB3C,QAAQ,IAAI,mBAAmB;IAwBzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,oBAAoB;IAwB5B,OAAO,CAAC,iBAAiB;IAMzB,cAAc,IAAI,gBAAgB,EAAE;IAIpC,SAAS,IAAI,OAAO;CAGrB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,CAAC,oBAAoB,CAAC,YAAK,EAC1C,MAAM,EAAE,OAAO,EACf,aAAa,GAAE,aAA4B,GAC1C,cAAc,CAEhB"}
1
+ {"version":3,"file":"connection-pool.d.ts","sourceRoot":"","sources":["../src/connection-pool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EAEpB,eAAe,EACf,OAAO,EACP,aAAa,EACd,MAAM,YAAY,CAAC;AAoDpB,qBAAa,cAAe,SAAQ,YAAa,YAAW,eAAe;IAmBvE,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAnBhC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6C;IACzE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAuB;IACtD,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,cAAc,CAAkB;IAExC,OAAO,CAAC,KAAK,CAOX;gBAGA,MAAM,EAAE,OAAO,CAAC,oBAAoB,CAAC,YAAK,EACzB,MAAM,EAAE,OAAO,EACf,aAAa,GAAE,aAA4B;YAQhD,wBAAwB;YAWxB,gBAAgB;IAexB,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAiC1C,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI;IAuB3C,OAAO,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI;IAoB3C,QAAQ,IAAI,mBAAmB;IAwBzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,oBAAoB;IAwB5B,OAAO,CAAC,iBAAiB;IAMzB,cAAc,IAAI,gBAAgB,EAAE;IAIpC,SAAS,IAAI,OAAO;CAGrB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,CAAC,oBAAoB,CAAC,YAAK,EAC1C,MAAM,EAAE,OAAO,EACf,aAAa,GAAE,aAA4B,GAC1C,cAAc,CAEhB"}
@@ -80,9 +80,12 @@ export class ConnectionPool extends EventEmitter {
80
80
  minConnections: this.config.minConnections,
81
81
  });
82
82
  }
83
- async createConnection() {
83
+ async createConnection(reserved = false) {
84
84
  const id = `conn-${++this.connectionCounter}-${Date.now()}`;
85
85
  const connection = new ManagedConnection(id, this.transportType);
86
+ // Reserve before exposing it in the map or yielding to another acquirer.
87
+ if (reserved)
88
+ connection.state = 'busy';
86
89
  this.connections.set(id, connection);
87
90
  this.stats.totalCreated++;
88
91
  this.emit('pool:connection:created', { connectionId: id });
@@ -105,7 +108,7 @@ export class ConnectionPool extends EventEmitter {
105
108
  }
106
109
  }
107
110
  if (this.connections.size < this.config.maxConnections) {
108
- const connection = await this.createConnection();
111
+ const connection = await this.createConnection(true);
109
112
  connection.acquire();
110
113
  this.stats.totalAcquired++;
111
114
  this.recordAcquireTime(startTime);