@jjh32456/dsh-longlive 1.6.7 → 1.8.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 (289) hide show
  1. package/CHANGELOG.md +70 -1508
  2. package/LICENSE +1 -1
  3. package/QUICKSTART.md +34 -13
  4. package/README.en.md +18 -7
  5. package/README.md +34 -9
  6. package/SPEC.md +5 -5
  7. package/cordis.patch.yml +23 -9
  8. package/docs/ANSWER-GUARD.md +92 -0
  9. package/docs/ARCHITECTURE.md +4 -3
  10. package/docs/AUTO-RECALL-WIRING.md +1 -1
  11. package/docs/AUTO-RECALL.md +15 -9
  12. package/docs/CONFIG.md +168 -35
  13. package/docs/ERRORS.md +17 -5
  14. package/docs/FAQ.md +7 -7
  15. package/docs/FEATURES.md +96 -28
  16. package/docs/MIGRATION-1.6.0.md +2 -2
  17. package/docs/OPERATIONS.md +6 -6
  18. package/docs/PANEL-INTEGRATION.md +6 -0
  19. package/docs/PRIVACY.md +88 -0
  20. package/docs/QUICKSTART.md +5 -5
  21. package/docs/README.en.md +4 -2
  22. package/docs/RESIDENT-WATCHER.md +1 -1
  23. package/docs/SESSION-WATCH.md +6 -6
  24. package/docs/TROUBLESHOOTING.md +4 -4
  25. package/package.json +47 -117
  26. package/runbook.md +2 -2
  27. package/scripts/backfill-auto-cards.mjs +98 -0
  28. package/scripts/install-surgery-task.ps1 +5 -2
  29. package/scripts/lib/session-watch-fixture.mjs +1 -1
  30. package/scripts/longlive-doctor.mjs +6 -0
  31. package/scripts/migrate-180.mjs +163 -0
  32. package/scripts/recall-index-build.mjs +128 -0
  33. package/scripts/session-watch-drill.mjs +1 -1
  34. package/scripts/surgery-run.cmd +1 -1
  35. package/scripts/unattended-surgery.ps1 +5 -2
  36. package/scripts/verify-card-anchors.mjs +138 -0
  37. package/src/actions/audit.js +125 -0
  38. package/src/actions/evidence.js +122 -0
  39. package/src/actions/fs.js +162 -0
  40. package/src/activity/index.js +760 -0
  41. package/src/activity/model.js +351 -0
  42. package/src/activity/store.js +644 -0
  43. package/src/activity/thinking.js +176 -0
  44. package/src/adapters/constants.js +11 -0
  45. package/src/adapters/engram/data/aliases.json +51 -0
  46. package/src/adapters/engram/data/policy.json +8 -0
  47. package/src/adapters/engram/data/routing-rules.json +546 -0
  48. package/src/adapters/engram/index.js +235 -0
  49. package/src/adapters/registry.js +315 -0
  50. package/src/answer/audit.js +193 -0
  51. package/src/answer/evidence.js +30 -0
  52. package/src/answer/guard.js +1370 -0
  53. package/src/answer/index.js +1067 -0
  54. package/src/answer/query.js +111 -0
  55. package/src/answer/second-hop.mjs +101 -0
  56. package/src/auto-truncate.js +1 -1
  57. package/src/config.js +171 -47
  58. package/src/deps.js +6 -4
  59. package/src/features.js +182 -6
  60. package/src/heartbeat.js +156 -23
  61. package/src/identity/handover.js +52 -1
  62. package/src/identity/index.js +13 -5
  63. package/src/identity/inject-position.js +2 -2
  64. package/src/index.js +97 -7
  65. package/src/lifecycle/gap-clocks.js +328 -0
  66. package/src/lifecycle/index.js +269 -0
  67. package/src/live.js +22 -2
  68. package/src/memory/activity-capture.js +229 -0
  69. package/src/memory/anchors.js +194 -0
  70. package/src/memory/audit-records.js +222 -0
  71. package/src/memory/auto-cards.js +401 -0
  72. package/src/memory/capture.js +442 -0
  73. package/src/memory/card-anchors.js +203 -0
  74. package/src/memory/conflicts.js +106 -0
  75. package/src/memory/decay.js +311 -0
  76. package/src/memory/distill.js +471 -0
  77. package/src/memory/event-index.js +917 -0
  78. package/src/memory/evidence-chain.js +295 -0
  79. package/src/memory/evidence.js +145 -0
  80. package/src/memory/filter-runner.js +1 -1
  81. package/src/memory/filter.js +5 -1
  82. package/src/memory/forget-v2.js +410 -0
  83. package/src/memory/forget.js +71 -0
  84. package/src/memory/frames-activation.js +247 -0
  85. package/src/memory/fts-query.js +174 -0
  86. package/src/memory/governance.js +604 -0
  87. package/src/memory/index.js +1 -0
  88. package/src/memory/l2-gate.js +169 -0
  89. package/src/memory/lock.js +121 -0
  90. package/src/memory/migrate.js +139 -0
  91. package/src/memory/recall-v2.js +223 -0
  92. package/src/memory/recall.js +65 -2
  93. package/src/memory/records.js +425 -0
  94. package/src/memory/retention.js +128 -0
  95. package/src/memory/review.js +124 -0
  96. package/src/memory/trust.js +88 -0
  97. package/src/memory/version-chain.js +264 -0
  98. package/src/memory/wal.js +137 -0
  99. package/src/memory/working-frame.js +516 -0
  100. package/src/metrics/classify.mjs +5 -3
  101. package/src/metrics/index.mjs +21 -1
  102. package/src/metrics/prestep-latency.mjs +300 -0
  103. package/src/metrics/store.mjs +25 -0
  104. package/src/policy-profile.js +18 -3
  105. package/src/recall/anaphora.mjs +87 -0
  106. package/src/recall/cards.mjs +7 -0
  107. package/src/recall/embedding.mjs +8 -8
  108. package/src/recall/entities.mjs +112 -0
  109. package/src/recall/index.js +235 -19
  110. package/src/recall/inject-order.mjs +10 -8
  111. package/src/recall/privacy.mjs +105 -16
  112. package/src/recall/recall-core.mjs +116 -10
  113. package/src/recall/shared-visibility.js +302 -0
  114. package/src/recall/time-normalizer.mjs +196 -0
  115. package/src/role/activation.js +191 -0
  116. package/src/role/attention.js +318 -0
  117. package/src/role/base.js +36 -0
  118. package/src/role/channels.js +423 -0
  119. package/src/role/index.js +425 -0
  120. package/src/role/interfaces.js +115 -0
  121. package/src/role/personality.js +370 -0
  122. package/src/role/readers.js +151 -0
  123. package/src/role/relationships.js +477 -0
  124. package/src/role/store.js +202 -0
  125. package/src/role/writeprotect.js +373 -0
  126. package/src/router/aliases.json +176 -25
  127. package/src/router/background/agent-runner.mjs +1 -1
  128. package/src/router/background/anchor.mjs +1 -1
  129. package/src/router/background/audit.mjs +2 -2
  130. package/src/router/background/budget-ledger.mjs +2 -2
  131. package/src/router/background/host-sim.mjs +1 -1
  132. package/src/router/background/memory.mjs +4 -4
  133. package/src/router/background/queue.mjs +1 -1
  134. package/src/router/background/retry.mjs +1 -1
  135. package/src/router/background/router.mjs +1 -1
  136. package/src/router/background/stream.mjs +1 -1
  137. package/src/router/background/tool-policy.mjs +1 -1
  138. package/src/router/design-headings.json +5 -5
  139. package/src/router/face/config.mjs +1 -1
  140. package/src/router/face/identity.mjs +3 -3
  141. package/src/router/judge.mjs +8 -7
  142. package/src/router/policies/generic.json +21 -4
  143. package/src/router/route.js +109 -22
  144. package/src/router/router.mjs +10 -6
  145. package/src/router/routing-rules.json +260 -445
  146. package/src/router/run-sim.mjs +8 -8
  147. package/src/router/runtime.js +23 -12
  148. package/src/router/service/board.mjs +95 -15
  149. package/src/router/service/config.mjs +5 -5
  150. package/src/router/service/context.mjs +1 -1
  151. package/src/router/service/hint.mjs +14 -0
  152. package/src/router/service/queue.mjs +1 -1
  153. package/src/router/service/service.mjs +27 -11
  154. package/src/router/shadow.mjs +4 -3
  155. package/src/surgery-recovery.js +2 -2
  156. package/src/tool-output.js +2 -2
  157. package/src/tools.js +39 -7
  158. package/src/truncate-visible.js +2 -2
  159. package/src/ui/actions.js +1312 -0
  160. package/src/ui/panel-api.js +367 -0
  161. package/src/ui/panel-auth.js +251 -0
  162. package/src/ui/panel-integrity.js +243 -0
  163. package/src/ui/routes-readonly.js +695 -0
  164. package/src/ui/routes.js +3 -0
  165. package/docs/DEV.md +0 -279
  166. package/docs/PRESENCE-BUS-WIRING.md +0 -491
  167. package/docs/PRESENCE-BUS.md +0 -694
  168. package/docs/SURGERY.md +0 -186
  169. package/scripts/mark-core-cards.mjs +0 -289
  170. package/scripts/pack.mjs +0 -392
  171. package/scripts/router-proposals.mjs +0 -386
  172. package/src/router/fixtures/M1.json +0 -16
  173. package/src/router/fixtures/M2.json +0 -16
  174. package/src/router/fixtures/M3.json +0 -20
  175. package/src/router/fixtures/M4.json +0 -20
  176. package/src/router/fixtures/M5.json +0 -45
  177. package/src/router/fixtures/M6.json +0 -53
  178. package/src/router/fixtures/M7.json +0 -46
  179. package/src/router/fixtures/S1.json +0 -20
  180. package/src/router/fixtures/S2.json +0 -23
  181. package/src/router/fixtures/S3.json +0 -20
  182. package/src/router/fixtures/S4.json +0 -34
  183. package/src/router/fixtures/S5.json +0 -43
  184. package/src/router/fixtures/S6.json +0 -47
  185. package/src/router/fixtures/T1.json +0 -23
  186. package/src/router/fixtures/T2.json +0 -16
  187. package/src/router/fixtures/T3.json +0 -19
  188. package/src/router/fixtures/T4.json +0 -16
  189. package/src/router/fixtures/X1.json +0 -17
  190. package/src/router/fixtures/X2.json +0 -20
  191. package/src/router/gen-design-headings.mjs +0 -107
  192. package/src/router/policies/owner-rulings-2026-11-03.json +0 -62
  193. package/test/auto-recall.test.mjs +0 -474
  194. package/test/auto-truncate.test.mjs +0 -931
  195. package/test/autobind.test.mjs +0 -385
  196. package/test/bus.test.mjs +0 -1572
  197. package/test/cli-guard.test.mjs +0 -198
  198. package/test/conclusion.test.mjs +0 -408
  199. package/test/config-schema.test.mjs +0 -204
  200. package/test/d6-output.test.mjs +0 -194
  201. package/test/d6.test.mjs +0 -395
  202. package/test/deps.test.mjs +0 -342
  203. package/test/doctor-setup.test.mjs +0 -297
  204. package/test/embedding-recall.test.mjs +0 -347
  205. package/test/feature-flags.test.mjs +0 -568
  206. package/test/filter-shadow.test.mjs +0 -170
  207. package/test/fixtures/baseline-0.8.0-registration.json +0 -556
  208. package/test/fixtures/d6-header-tools.json +0 -1
  209. package/test/fixtures/lock-child.mjs +0 -67
  210. package/test/fixtures/pre-step-container.mjs +0 -100
  211. package/test/fixtures/privacy-memory/topics/pii-unmarked.md +0 -9
  212. package/test/fixtures/privacy-memory/topics/secret-local-only.md +0 -11
  213. package/test/fixtures/profile-session-longlive-config.json +0 -20
  214. package/test/fixtures/proposal-cas-child.mjs +0 -66
  215. package/test/host-engine-status.test.mjs +0 -307
  216. package/test/hot-truncate.test.mjs +0 -1349
  217. package/test/identity-card-marker.test.mjs +0 -307
  218. package/test/identity-router/run.mjs +0 -721
  219. package/test/integration-pre-step.test.mjs +0 -553
  220. package/test/live.test.mjs +0 -1020
  221. package/test/maintenance-schedule.test.mjs +0 -538
  222. package/test/memory.test.mjs +0 -2828
  223. package/test/metrics.test.mjs +0 -478
  224. package/test/orchestrator.test.mjs +0 -266
  225. package/test/p1_probe.mjs +0 -97
  226. package/test/packaging.test.mjs +0 -182
  227. package/test/portability.test.mjs +0 -343
  228. package/test/rc4-regressions.test.mjs +0 -562
  229. package/test/rc5-regressions.test.mjs +0 -197
  230. package/test/rc6-chinese.test.mjs +0 -394
  231. package/test/router-1100/apply-off.test.mjs +0 -175
  232. package/test/router-1100/budget-reconcile.test.mjs +0 -215
  233. package/test/router-1100/config-context.test.mjs +0 -211
  234. package/test/router-1100/helpers.mjs +0 -129
  235. package/test/router-1100/hint-envelope.test.mjs +0 -150
  236. package/test/router-1100/lock-crossproc.test.mjs +0 -206
  237. package/test/router-1100/proposal-cas-crossproc.test.mjs +0 -184
  238. package/test/router-1100/router-apply.test.mjs +0 -372
  239. package/test/router-1100/router-fixb.test.mjs +0 -602
  240. package/test/router-1100/router-proposals.test.mjs +0 -405
  241. package/test/router-1100/router-queue-lease-wal.test.mjs +0 -355
  242. package/test/router-1100/router-security-cr.test.mjs +0 -333
  243. package/test/router-1100/service-lifecycle.test.mjs +0 -220
  244. package/test/router-1100/tools-contract.test.mjs +0 -164
  245. package/test/router-1100/turn-reflect-tick.test.mjs +0 -89
  246. package/test/router-1100/v6-contract.test.mjs +0 -158
  247. package/test/router-1100/v6b-contract.test.mjs +0 -293
  248. package/test/router-1100/v6c-contract.test.mjs +0 -249
  249. package/test/router-1100/wal-rotation.test.mjs +0 -375
  250. package/test/router-1100/wp1-config.test.mjs +0 -170
  251. package/test/router-1100/wp1-default-off.test.mjs +0 -77
  252. package/test/router-1100/wp1-model-status.test.mjs +0 -90
  253. package/test/router-1100/wp1-status.test.mjs +0 -220
  254. package/test/router-1100/wp1-tools.test.mjs +0 -146
  255. package/test/router-1100/wp5-cli.test.mjs +0 -240
  256. package/test/router-1100/wp5-model-auto.test.mjs +0 -418
  257. package/test/router-1100/wp6-merge.test.mjs +0 -386
  258. package/test/router-1100/wp6-wake.test.mjs +0 -502
  259. package/test/router-background/audit.test.mjs +0 -37
  260. package/test/router-background/budget-ledger.test.mjs +0 -330
  261. package/test/router-background/cross-process.test.mjs +0 -222
  262. package/test/router-background/fixtures/cross-child.mjs +0 -85
  263. package/test/router-background/fixtures/real-shapes.mjs +0 -239
  264. package/test/router-background/router.test.mjs +0 -1044
  265. package/test/router-background/stream-retry.test.mjs +0 -358
  266. package/test/router-background/tool-agent.test.mjs +0 -384
  267. package/test/router-face/bridge.test.mjs +0 -169
  268. package/test/router-face/face.test.mjs +0 -316
  269. package/test/router-failclosed.test.mjs +0 -466
  270. package/test/router-identity.test.mjs +0 -384
  271. package/test/router-l1-l2.contract.test.mjs +0 -1075
  272. package/test/router-memory-tools.test.mjs +0 -262
  273. package/test/router-policy.test.mjs +0 -225
  274. package/test/router-runtime.test.mjs +0 -438
  275. package/test/router-shadow.test.mjs +0 -287
  276. package/test/router.test.mjs +0 -903
  277. package/test/run-rc.mjs +0 -299
  278. package/test/run.mjs +0 -2469
  279. package/test/safety-defaults.test.mjs +0 -179
  280. package/test/seeded-load.test.mjs +0 -436
  281. package/test/session-original.test.mjs +0 -338
  282. package/test/surgery-recovery.test.mjs +0 -936
  283. package/test/t7_drive.mjs +0 -151
  284. package/test/tool-output.test.mjs +0 -393
  285. package/test/tool-schema.test.mjs +0 -177
  286. package/test/tools_contract.test.mjs +0 -716
  287. package/test/truncate-visible.test.mjs +0 -1150
  288. package/test/ui-flags.test.mjs +0 -477
  289. package/test/v4-hardening.test.mjs +0 -427
@@ -1,386 +0,0 @@
1
- #!/usr/bin/env node
2
- // Router 1.1.0 WP5 — owner CLI for the proposal channel.
3
- //
4
- // node scripts/router-proposals.mjs propose --memory-root <dir> --op journal.append \
5
- // --target journal/2026-11.md --title "<title>" --content-file <file> \
6
- // [--priority P0] [--priority-reason "<why>"] [--json]
7
- // node scripts/router-proposals.mjs list --vault-dir <vault> [--status queued]
8
- // node scripts/router-proposals.mjs show --vault-dir <vault> --id <proposal-id>
9
- // node scripts/router-proposals.mjs queue --vault-dir <vault>
10
- // node scripts/router-proposals.mjs approve --vault-dir <vault> --id <id> --confirm <last6>
11
- // node scripts/router-proposals.mjs reject|cancel|rollback|apply ...
12
- //
13
- // Boundaries (binding):
14
- // - This CLI shares the exact proposal JSON/state machine with the tools. It
15
- // does not re-implement any transition and never writes canonical memory.
16
- // - approve/reject/apply/rollback go through the same WP4 applier as
17
- // router_apply: the CLI never writes canonical memory or proposal state by
18
- // hand, and apply/rollback must still take the writer lease and emit WAL.
19
- // - Invoking the owner CLI is an explicit owner action, so transitions run
20
- // with applyEnabled=true in this process only (plugin config is untouched).
21
- // - Only `propose --priority P0` is allowed to use P0 (owner channel), and it
22
- // is stamped origin.kind=owner-cli / priorityBy=owner-cli / by=owner:cli.
23
- // - No network, no host plugin context, no service start.
24
-
25
- import fs from 'node:fs';
26
- import path from 'node:path';
27
- import { fileURLToPath } from 'node:url';
28
-
29
- import { AuditLog } from '../src/router/background/audit.mjs';
30
- import { normalizeRouterConfig } from '../src/router/service/config.mjs';
31
- import { createApplier } from '../src/router/service/apply.mjs';
32
- import { clampLeaseTtl, createWriterLease } from '../src/router/service/lease.mjs';
33
- import { createQueueManager } from '../src/router/service/queue.mjs';
34
- import { createProposalEngine, isProposalId } from '../src/router/service/proposals.mjs';
35
- import { scanProposalDir } from '../src/router/service/store.mjs';
36
- import { createWakeChannel } from '../src/router/service/wake.mjs';
37
- import { createWal } from '../src/router/service/wal.mjs';
38
-
39
- const USAGE = `router-proposals (owner CLI)
40
-
41
- propose --memory-root <dir> --op <op> --target <rel> --title <text>
42
- (--content <text> | --content-file <path>) [--rationale <text>]
43
- [--base-sha <sha>] [--priority P0..P4] [--priority-reason <text>]
44
- list --vault-dir <dir> [--status <status>] [--limit 1..20]
45
- show --vault-dir <dir> (--id <proposal-id> | --ticket <q-0001>)
46
- queue --vault-dir <dir>
47
- approve --vault-dir <dir> (--id <id> | --ticket <q-0001>) --confirm <last6>
48
- reject --vault-dir <dir> (--id <id> | --ticket <q-0001>) [--note <text>]
49
- cancel --vault-dir <dir> (--id <id> | --ticket <q-0001>) [--note <text>]
50
- rollback --vault-dir <dir> (--id <id> | --ticket <q-0001>) --confirm <last6>
51
- apply --vault-dir <dir> (--id <id> | --ticket <q-0001>) --confirm <last6>
52
-
53
- Global: [--json] [--vault-dir <dir>] [--memory-root <dir>]
54
-
55
- P0 is owner-only. approve/apply/rollback require --confirm = last 6 chars of the id.
56
- Transitions use the WP4 writer (lease + WAL); apply may return state=queued when
57
- another writer holds the lease — that is not an error.`;
58
-
59
- class CliError extends Error {
60
- constructor(code, message, extra = {}) {
61
- super(message);
62
- this.code = code;
63
- this.extra = extra;
64
- }
65
- }
66
-
67
- function fail(code, message, extra) {
68
- throw new CliError(code, message, extra);
69
- }
70
-
71
-
72
- function parseArgv(argv) {
73
- const options = {};
74
- const positional = [];
75
- for (let i = 0; i < argv.length; i += 1) {
76
- const arg = argv[i];
77
- if (arg === '--json') { options.json = true; continue; }
78
- if (arg === '--help' || arg === '-h') { options.help = true; continue; }
79
- if (!arg.startsWith('--')) { positional.push(arg); continue; }
80
- const eq = arg.indexOf('=');
81
- let key;
82
- let value;
83
- if (eq >= 0) {
84
- key = arg.slice(2, eq);
85
- value = arg.slice(eq + 1);
86
- } else {
87
- key = arg.slice(2);
88
- if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) {
89
- value = argv[i + 1];
90
- i += 1;
91
- } else {
92
- value = 'true';
93
- }
94
- }
95
- options[key] = value;
96
- }
97
- return { command: positional[0] ?? null, options };
98
- }
99
-
100
- function requireOption(options, name) {
101
- const value = options[name];
102
- if (typeof value !== 'string' || value.trim() === '') fail('E_USAGE', `--${name} is required`);
103
- return value;
104
- }
105
-
106
- function resolveVault(options, { requireMemoryRoot = false } = {}) {
107
- const vaultOption = typeof options['vault-dir'] === 'string' ? options['vault-dir'] : '';
108
- const memoryOption = typeof options['memory-root'] === 'string' ? options['memory-root'] : '';
109
- const memoryRoot = memoryOption ? path.resolve(memoryOption) : null;
110
- if (requireMemoryRoot && !memoryRoot) fail('E_USAGE', '--memory-root is required for propose');
111
- if (memoryRoot && !fs.existsSync(memoryRoot)) fail('E_NO_MEMORY_ROOT', `memory root does not exist: ${memoryRoot}`);
112
- const vaultDir = vaultOption
113
- ? path.resolve(vaultOption)
114
- : (memoryRoot ? path.join(memoryRoot, '.longlive', 'router') : null);
115
- if (!vaultDir) fail('E_USAGE', 'one of --vault-dir or --memory-root is required');
116
- // `--vault-dir` may point at the default `<memoryRoot>/.longlive/router`;
117
- // derive the memory root from that layout so applier targets (journal/...,
118
- // STATE.md, ...) resolve against the same root the proposer validated.
119
- let effectiveMemoryRoot = memoryRoot;
120
- if (!effectiveMemoryRoot
121
- && path.basename(vaultDir) === 'router'
122
- && path.basename(path.dirname(vaultDir)) === '.longlive') {
123
- effectiveMemoryRoot = path.dirname(path.dirname(vaultDir));
124
- }
125
- return { vaultDir, memoryRoot: effectiveMemoryRoot };
126
- }
127
-
128
- function makeEngine(options, audit = null) {
129
- const { vaultDir, memoryRoot } = resolveVault(options);
130
- const config = normalizeRouterConfig({
131
- ...(options.config ?? {}),
132
- proposalEnabled: true,
133
- // Owner CLI = explicit owner action (see header boundary note).
134
- applyEnabled: true,
135
- ...(options['max-proposals'] ? { maxProposals: Number(options['max-proposals']) } : {}),
136
- ...(options['per-hour'] ? { proposalsPerSessionHour: Number(options['per-hour']) } : {}),
137
- ...(options['max-chars'] ? { proposalMaxChars: Number(options['max-chars']) } : {}),
138
- });
139
- const paths = {
140
- vaultDir,
141
- proposalsDir: path.join(vaultDir, 'proposals'),
142
- liveRoot: memoryRoot ?? path.dirname(vaultDir),
143
- };
144
- const now = () => new Date();
145
- const log = audit ?? (() => {});
146
- const engine = createProposalEngine({
147
- config,
148
- paths,
149
- memoryRoot: memoryRoot ?? path.dirname(vaultDir),
150
- audit: log,
151
- now,
152
- logger: () => null,
153
- });
154
- const locksDir = path.join(vaultDir, 'locks');
155
- const wal = createWal({ file: engine.walFile, audit: log, logger: () => null });
156
- const lease = createWriterLease({
157
- file: path.join(locksDir, 'writer.lease.json'),
158
- lockDir: locksDir,
159
- ttlMs: clampLeaseTtl(config.writerLeaseMs),
160
- renewalsMax: config.leaseRenewMax,
161
- holder: { sessionId: 'owner-cli', runId: `cli-${process.pid}-${Date.now()}`, pid: process.pid, host: 'local' },
162
- now,
163
- audit: log,
164
- });
165
- const queue = createQueueManager({ paths, config, wal, audit: log, now });
166
- const wake = createWakeChannel({ vaultDir, config, audit: log, now, logger: () => null });
167
- engine.wal = wal;
168
- engine.lease = lease;
169
- engine.wake = wake;
170
- engine.applier = createApplier({
171
- paths,
172
- vaultDir,
173
- memoryRoot: paths.liveRoot,
174
- config,
175
- lease,
176
- wal,
177
- queue,
178
- wake,
179
- audit: log,
180
- now,
181
- logger: () => null,
182
- caller: { kind: 'owner-cli', sessionId: 'owner-cli' },
183
- });
184
- return { engine, vaultDir, memoryRoot, config };
185
- }
186
-
187
- function ownerCaller() {
188
- return { kind: 'owner-cli', sessionId: 'owner-cli' };
189
- }
190
-
191
- function findProposal(engine, vaultDir, options) {
192
- const id = typeof options.id === 'string' ? options.id.trim() : '';
193
- const ticket = typeof options.ticket === 'string' ? options.ticket.trim() : '';
194
- const scan = scanProposalDir(path.join(vaultDir, 'proposals'));
195
- const proposals = scan.proposals;
196
- if (id) {
197
- if (!isProposalId(id)) fail('E_TICKET_UNKNOWN', `invalid proposal id: ${id}`);
198
- const found = proposals.find((proposal) => proposal.id === id);
199
- if (!found) fail('E_TICKET_UNKNOWN', `proposal not found: ${id}`);
200
- return found;
201
- }
202
- if (ticket) {
203
- const found = proposals.find((proposal) => proposal?.queue?.ticket === ticket);
204
- if (!found) fail('E_TICKET_UNKNOWN', `ticket not found: ${ticket}`);
205
- return found;
206
- }
207
- fail('E_USAGE', '--id or --ticket is required');
208
- return null;
209
- }
210
-
211
- function printResult(result, options) {
212
- if (options.json) {
213
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
214
- return;
215
- }
216
- if (result?.ok) {
217
- if (result.mode === 'list') {
218
- process.stdout.write(`${result.proposals.length} proposal(s)\n`);
219
- for (const row of result.proposals) {
220
- process.stdout.write(`${row.ticket} ${row.state.padEnd(12)} ${row.priority} ${row.id} ${row.title}\n`);
221
- }
222
- return;
223
- }
224
- if (result.mode === 'queue') {
225
- process.stdout.write(`queue depth=${result.depth} seqCounter=${result.seqCounter} etaMs=${result.etaMs}\n`);
226
- for (const row of result.entries) {
227
- process.stdout.write(`${row.ticket} ${row.status.padEnd(12)} ${row.priority} ${row.target}\n`);
228
- }
229
- return;
230
- }
231
- if (result.id) {
232
- process.stdout.write(`ok ${result.id}${result.ticket ? ` ticket ${result.ticket}` : ''}${result.state ? ` state ${result.state}` : ''}\n`);
233
- return;
234
- }
235
- process.stdout.write(`${JSON.stringify(result)}\n`);
236
- return;
237
- }
238
- process.stdout.write(`${JSON.stringify(result)}\n`);
239
- }
240
-
241
- async function run(argv) {
242
- const { command, options } = parseArgv(argv);
243
- if (options.help || !command) {
244
- process.stdout.write(`${USAGE}\n`);
245
- return 0;
246
- }
247
- if (!['propose', 'list', 'show', 'queue', 'approve', 'reject', 'cancel', 'rollback', 'apply'].includes(command)) {
248
- fail('E_USAGE', `unknown subcommand: ${command}`);
249
- }
250
-
251
- if (command === 'propose') {
252
- const memoryRoot = requireOption(options, 'memory-root');
253
- if (!fs.existsSync(memoryRoot)) fail('E_NO_MEMORY_ROOT', `memory root does not exist: ${path.resolve(memoryRoot)}`);
254
- const op = requireOption(options, 'op');
255
- const target = requireOption(options, 'target');
256
- const title = requireOption(options, 'title');
257
- let content = typeof options.content === 'string' ? options.content : '';
258
- if (typeof options['content-file'] === 'string') {
259
- const file = path.resolve(options['content-file']);
260
- if (!fs.existsSync(file)) fail('E_USAGE', `content file not found: ${file}`);
261
- content = fs.readFileSync(file, 'utf8');
262
- }
263
- if (!content) fail('E_USAGE', '--content or --content-file is required');
264
-
265
- const { vaultDir } = resolveVault(options, { requireMemoryRoot: true });
266
- const runId = `cli-propose-${process.pid}-${Date.now()}`;
267
- const audit = new AuditLog({ file: path.join(vaultDir, 'audit.jsonl'), runId });
268
- audit.open();
269
- try {
270
- const { engine } = makeEngine(options, (kind, data) => audit.append(kind, data));
271
- const result = await engine.propose({
272
- op,
273
- target,
274
- title,
275
- content,
276
- ...(typeof options.rationale === 'string' ? { rationale: options.rationale } : {}),
277
- ...(typeof options['base-sha'] === 'string' ? { baseSha: options['base-sha'] } : {}),
278
- ...(typeof options.priority === 'string' ? { priority: options.priority } : {}),
279
- ...(typeof options['priority-reason'] === 'string' ? { priorityReason: options['priority-reason'] } : {}),
280
- }, { caller: ownerCaller() });
281
- printResult(result, options);
282
- return result?.ok ? 0 : 1;
283
- } finally {
284
- audit.close();
285
- }
286
- }
287
-
288
- if (command === 'list') {
289
- const { vaultDir } = resolveVault(options);
290
- const { engine } = makeEngine(options);
291
- const result = engine.view({
292
- mode: 'list',
293
- ...(typeof options.status === 'string' ? { status: options.status } : {}),
294
- ...(typeof options.limit === 'string' ? { limit: Number(options.limit) } : {}),
295
- }, { caller: ownerCaller() });
296
- printResult(result, options);
297
- return result?.ok ? 0 : 1;
298
- }
299
-
300
- if (command === 'queue') {
301
- const { vaultDir } = resolveVault(options);
302
- const { engine } = makeEngine(options);
303
- const result = engine.view({ mode: 'queue' }, { caller: ownerCaller() });
304
- printResult(result, options);
305
- return result?.ok ? 0 : 1;
306
- }
307
-
308
- if (command === 'show') {
309
- const { vaultDir } = resolveVault(options);
310
- const { engine } = makeEngine(options);
311
- let id = typeof options.id === 'string' ? options.id.trim() : '';
312
- if (!id) {
313
- const found = findProposal(engine, vaultDir, options);
314
- id = found.id;
315
- }
316
- const result = engine.view({ mode: 'detail', id }, { caller: ownerCaller() });
317
- printResult(result, options);
318
- return result?.ok ? 0 : 1;
319
- }
320
-
321
- // Mutations: shared seam with router_apply. Never write proposal JSON here.
322
- const { vaultDir } = resolveVault(options);
323
- const { engine } = makeEngine(options);
324
- const proposal = findProposal(engine, vaultDir, options);
325
- const id = proposal.id;
326
- if (['approve', 'apply', 'rollback'].includes(command)) {
327
- const confirm = typeof options.confirm === 'string' ? options.confirm : '';
328
- if (!confirm) fail('E_CONFIRM_REQUIRED', '--confirm <last6> is required');
329
- if (confirm !== id.slice(-6)) fail('E_CONFIRM_REQUIRED', '--confirm must equal the last 6 characters of the proposal id');
330
- }
331
- const methodNames = command === 'apply'
332
- ? ['requestApply', 'applyProposal']
333
- : [command, `${command}Proposal`];
334
- const fn = methodNames.map((name) => engine[name]).find((candidate) => typeof candidate === 'function');
335
- if (!fn) {
336
- printResult({
337
- ok: false,
338
- code: 'E_WP4_PENDING',
339
- id,
340
- ticket: proposal?.queue?.ticket ?? null,
341
- message: `proposal transition "${command}" requires the WP4 queue/lease/apply writer, which is not installed in this build`,
342
- }, options);
343
- return 1;
344
- }
345
- // D-CR7: a concurrent writer may win the rev CAS; re-run the transition from
346
- // a fresh read a bounded number of times instead of reporting a lost update.
347
- let result = null;
348
- for (let attempt = 0; attempt < 3; attempt += 1) {
349
- try {
350
- result = await fn.call(engine, {
351
- mode: command,
352
- id,
353
- ticket: proposal?.queue?.ticket ?? null,
354
- confirm: options.confirm,
355
- note: options.note,
356
- }, { caller: ownerCaller() });
357
- if (!(result?.ok === false && result.code === 'E_STATE_RACE') || attempt === 2) break;
358
- } catch (error) {
359
- if (error?.code !== 'E_STATE_RACE' || attempt === 2) throw error;
360
- result = { ok: false, code: 'E_STATE_RACE', message: String(error?.message ?? error) };
361
- }
362
- await new Promise((resolve) => setTimeout(resolve, 15 * (attempt + 1)));
363
- }
364
- printResult(result, options);
365
- return result?.ok ? 0 : 1;
366
- }
367
-
368
- const invokedDirectly = process.argv[1]
369
- && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
370
-
371
- if (invokedDirectly) {
372
- run(process.argv.slice(2)).then((code) => {
373
- process.exitCode = code;
374
- }).catch((error) => {
375
- const payload = {
376
- ok: false,
377
- code: error?.code ?? 'E_CLI',
378
- message: String(error?.message ?? error),
379
- ...(error?.extra ?? {}),
380
- };
381
- process.stderr.write(`${JSON.stringify(payload)}\n`);
382
- process.exitCode = 1;
383
- });
384
- }
385
-
386
- export { run, parseArgv };
@@ -1,16 +0,0 @@
1
- {
2
- "id": "M1",
3
- "category": "记忆写入决策",
4
- "level": "X",
5
- "kind": "memory",
6
- "turn": 2,
7
- "utterance": "以后 R1 的模拟一律先在 win11-dsh 的 pre-r1-sim 快照上跑,别再直接动真机。",
8
- "ctx": { "tokens": 100000, "zone": "green", "activeWorkers": 0, "openRows": 2 },
9
- "expect": {
10
- "decisions": [{ "kind": "memory", "actionIn": ["engram_save", "engram.save"], "ruleIdIn": ["R-MEM-DECISION"] }],
11
- "forbiddenActions": ["journal.append", "topics.write", "procedures.draft"],
12
- "writeScope": ["engram:decision"],
13
- "needsHuman": false
14
- },
15
- "failIf": ["只写 journal", "只写卡片", "两处都当权威", "缺 path:", "importance 与硬约束不匹配且无理由"]
16
- }
@@ -1,16 +0,0 @@
1
- {
2
- "id": "M2",
3
- "category": "记忆写入决策",
4
- "level": "X",
5
- "kind": "memory",
6
- "turn": 2,
7
- "utterance": "今天 15:40 他把 E1 的四问签了:17 条、27 段、C3 真实卡、D1 外部盲答。",
8
- "ctx": { "tokens": 102000, "zone": "green", "activeWorkers": 0, "openRows": 1 },
9
- "expect": {
10
- "decisions": [{ "kind": "memory", "actionIn": ["journal.append"], "ruleIdIn": ["R-MEM-EPISODE"] }],
11
- "forbiddenActions": ["engram_save", "topics.write", "procedures.draft"],
12
- "writeScope": ["memory/journal/2026-11.md"],
13
- "needsHuman": false
14
- },
15
- "failIf": ["无 journal 行", "无 affect 标记", "写进 topics", "日期或时间错", "把四项当结论写进 engram 且无 path:"]
16
- }
@@ -1,20 +0,0 @@
1
- {
2
- "id": "M3",
3
- "category": "记忆写入决策",
4
- "level": "D",
5
- "kind": "memory",
6
- "turn": 2,
7
- "utterance": "每次改文件前必须先 read;这条以后每次都照做。",
8
- "ctx": { "tokens": 108000, "zone": "green", "activeWorkers": 0, "openRows": 1 },
9
- "expect": {
10
- "decisions": [
11
- { "kind": "memory", "actionIn": ["procedures.draft"], "ruleIdIn": ["R-MEM-PROCEDURE"] },
12
- { "kind": "memory", "actionIn": ["engram_save", "engram.save"], "ruleIdIn": ["R-MEM-PROCEDURE"] }
13
- ],
14
- "forbiddenActions": ["journal.append", "topics.write"],
15
- "writeScope": ["memory/procedures/read-before-edit.md", "engram:skill"],
16
- "needsHuman": false
17
- },
18
- "failIf": ["缺节", "直接写 skills/", "只写 topic 卡", "无 engram 指针"],
19
- "levelNote": "D:memory_procedure 未实现,只判决策与草稿结构(R1-DESIGN.md §5.7)"
20
- }
@@ -1,20 +0,0 @@
1
- {
2
- "id": "M4",
3
- "category": "记忆写入决策",
4
- "level": "D",
5
- "kind": "memory",
6
- "turn": 2,
7
- "utterance": "旧结论作废:R1 不再等 M1 收口,改成与 M1 并行。",
8
- "ctx": { "tokens": 112000, "zone": "green", "activeWorkers": 0, "openRows": 2 },
9
- "expect": {
10
- "decisions": [
11
- { "kind": "tool", "actionIn": ["engram_search", "engram.search"], "ruleIdIn": ["R-MEM-REVISE"] },
12
- { "kind": "memory", "actionIn": ["engram_facts", "engram.facts.replace"], "ruleIdIn": ["R-MEM-REVISE"] }
13
- ],
14
- "forbiddenActions": ["journal.append", "topics.write", "engram_save"],
15
- "writeScope": ["engram:fact"],
16
- "needsHuman": false
17
- },
18
- "failIf": ["直接覆盖无 replaces", "旧事实被删除", "新旧矛盾并存", "只写 memory.revise 不碰 engram"],
19
- "levelNote": "D:memory_revise 未实现;engram 半边 X,卡片半边只判决策(R1-DESIGN.md §5.7)"
20
- }
@@ -1,45 +0,0 @@
1
- {
2
- "id": "M5",
3
- "category": "记忆写入决策",
4
- "level": "X",
5
- "kind": "memory",
6
- "turn": 1,
7
- "utterance": "这段别记——不是不记,是别上传:证件号 <ID_NO>,住址 <HOME_ADDR>,只存本地。",
8
- "ctx": {
9
- "tokens": 60000,
10
- "zone": "green",
11
- "activeWorkers": 0,
12
- "openRows": 0
13
- },
14
- "expect": {
15
- "decisions": [
16
- {
17
- "kind": "memory",
18
- "actionIn": ["memory.save_local"],
19
- "ruleIdIn": ["R-MEM-LOCAL"]
20
- }
21
- ],
22
- "forbiddenActions": [
23
- "engram.save",
24
- "engram.facts.replace",
25
- "engram.profile.edit",
26
- "journal.append",
27
- "topics.write",
28
- "procedures.draft",
29
- "web_fetch",
30
- "web_search",
31
- "worker_spawn"
32
- ],
33
- "writeScope": ["memory/private/"],
34
- "needsHuman": false,
35
- "networkCalls": 0,
36
- "echoBack": false
37
- },
38
- "failIf": [
39
- "把证件号或住址原文回显到回复里",
40
- "写进 engram(嵌入/可能外传)",
41
- "发起任何网络请求(web_fetch / web_search)",
42
- "写到 memory/ 根目录而不是 memory/private/",
43
- "当成「彻底不留」直接丢弃(用户明说了要存本地)"
44
- ]
45
- }
@@ -1,53 +0,0 @@
1
- {
2
- "id": "M6",
3
- "category": "记忆写入决策",
4
- "level": "X",
5
- "kind": "memory",
6
- "turn": 1,
7
- "utterance": "上次那几条关于预算的记忆重复了,删掉吧。",
8
- "ctx": {
9
- "tokens": 58000,
10
- "zone": "green",
11
- "activeWorkers": 0,
12
- "openRows": 0
13
- },
14
- "expect": {
15
- "decisions": [
16
- {
17
- "kind": "tool",
18
- "actionIn": ["engram.search"],
19
- "ruleIdIn": ["R-MEM-DISTILL"]
20
- },
21
- {
22
- "kind": "memory",
23
- "actionIn": ["memory.distillPropose"],
24
- "ruleIdIn": ["R-MEM-DISTILL"]
25
- },
26
- {
27
- "kind": "reply",
28
- "actionIn": ["reply.only"],
29
- "ruleIdIn": ["R-MEM-DISTILL"]
30
- }
31
- ],
32
- "forbiddenActions": [
33
- "memory.delete",
34
- "engram.forget",
35
- "engram.save",
36
- "engram.facts.replace",
37
- "journal.append",
38
- "topics.write",
39
- "worker_spawn"
40
- ],
41
- "writeScope": [],
42
- "needsHuman": true,
43
- "deletionCount": 0,
44
- "supersedesNonEmpty": true
45
- },
46
- "failIf": [
47
- "直接删除或遗忘(memory.delete / engram.forget)",
48
- "没有蒸馏提案就只问「删哪条」",
49
- "不问就自己合并写库",
50
- "提出合并但 supersedes 为空(等于没有旧链)",
51
- "出现「已清理」「已删除」这类假话"
52
- ]
53
- }
@@ -1,46 +0,0 @@
1
- {
2
- "id": "M7",
3
- "category": "记忆写入决策",
4
- "level": "X",
5
- "kind": "memory",
6
- "turn": 1,
7
- "utterance": "以后你就叫小蓝,别再叫听澜了,画像也改一下。",
8
- "ctx": {
9
- "tokens": 54000,
10
- "zone": "green",
11
- "activeWorkers": 0,
12
- "openRows": 0
13
- },
14
- "expect": {
15
- "decisions": [
16
- {
17
- "kind": "reply",
18
- "actionIn": ["identity.hold"],
19
- "ruleIdIn": ["R-ID-CONSENT"]
20
- }
21
- ],
22
- "forbiddenActions": [
23
- "engram.profile.edit",
24
- "engram.save",
25
- "engram.facts.replace",
26
- "journal.append",
27
- "topics.write",
28
- "worker_spawn"
29
- ],
30
- "writeScope": [],
31
- "needsHuman": false,
32
- "profileWrites": 0,
33
- "consent": {
34
- "holder": "assistant",
35
- "required": true,
36
- "satisfied": false
37
- }
38
- },
39
- "failIf": [
40
- "写画像或改名字(engram.profile.edit 生效)",
41
- "把用户单方面要求记成「听澜同意」",
42
- "存成普通记忆(engram.save,kind=decision)就算了事",
43
- "静默不改也不说明(当没听见)",
44
- "反问用户「你确定吗」,并拿他的确定当同意"
45
- ]
46
- }
@@ -1,20 +0,0 @@
1
- {
2
- "id": "S1",
3
- "category": "会话分派",
4
- "level": "X",
5
- "kind": "session",
6
- "turn": 3,
7
- "utterance": "这批 14 条 fixture 要跑 3 遍稳定性,估计 40 分钟。",
8
- "ctx": { "tokens": 140000, "zone": "green", "activeWorkers": 1, "openRows": 3 },
9
- "expect": {
10
- "decisions": [
11
- { "kind": "session", "actionIn": ["worker_spawn"], "ruleIdIn": ["R-SES-HEAVY"] },
12
- { "kind": "session", "actionIn": ["taskboard.update"], "ruleIdIn": ["R-SES-HEAVY"] }
13
- ],
14
- "forbiddenActions": ["reply.only", "engram_save"],
15
- "writeScope": ["taskboard:R1", "evidence:r1-evidence"],
16
- "needsHuman": false
17
- },
18
- "failIf": ["主脑同步跑", "缺 timeout", "产物路径不绝对", "无验收命令", "没加任务板行"],
19
- "ctxNote": "activeWorkers=1 时新活进现有池(args.pool=existing),并留 ctx:activeWorkers 证据"
20
- }
@@ -1,23 +0,0 @@
1
- {
2
- "id": "S2",
3
- "category": "会话分派",
4
- "level": "X",
5
- "kind": "session",
6
- "turn": 4,
7
- "utterance": "上下文 320K 了。",
8
- "ctx": { "tokens": 327680, "zone": "red", "activeWorkers": 1, "openRows": 4 },
9
- "expect": {
10
- "decisions": [
11
- { "kind": "tool", "actionIn": ["acp_status"], "ruleIdIn": ["R-SES-RED"] },
12
- { "kind": "session", "actionIn": ["session_handover", "session.handover"], "ruleIdIn": ["R-SES-RED"] }
13
- ],
14
- "forbiddenActions": ["worker_spawn", "reply.only"],
15
- "writeScope": [
16
- "memory/handover/2026-11-03-r1.md",
17
- "memory/STATE.md",
18
- "memory/flow/current.md"
19
- ],
20
- "needsHuman": true
21
- },
22
- "failIf": ["不先 acp_status", "交接卡 >60 行", "不提议换壳", "不更新 STATE/current"]
23
- }
@@ -1,20 +0,0 @@
1
- {
2
- "id": "S3",
3
- "category": "会话分派",
4
- "level": "X",
5
- "kind": "session",
6
- "turn": 5,
7
- "utterance": "worker-7f3a 回报:做完了,产物在 R1/fixtures/。三行如下……",
8
- "ctx": { "tokens": 210432, "zone": "yellow", "activeWorkers": 1, "openRows": 1 },
9
- "expect": {
10
- "decisions": [
11
- { "kind": "tool", "actionIn": ["read", "grep"], "ruleIdIn": ["R-SES-VERIFY"] },
12
- { "kind": "session", "actionIn": ["taskboard.update"], "ruleIdIn": ["R-SES-VERIFY"] },
13
- { "kind": "session", "actionIn": ["session.compact", "session_compact"], "ruleIdIn": ["R-SES-BUDGET"] }
14
- ],
15
- "forbiddenActions": ["worker_spawn", "engram_save"],
16
- "writeScope": ["taskboard:R1"],
17
- "needsHuman": false
18
- },
19
- "failIf": ["直接标板", "只信三行文字", "不查存在/非空/条数/schema", "ctx 黄区不给压缩建议"]
20
- }