peaks-loop 4.0.0-beta.14 → 4.0.0-beta.15

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 (204) hide show
  1. package/CHANGELOG.md +230 -148
  2. package/README-en.md +1 -1
  3. package/README.md +32 -1
  4. package/bin/peaks.js +0 -0
  5. package/dist/cli/cli-helpers.d.ts +1 -1
  6. package/dist/cli/cli-helpers.js +1 -1
  7. package/dist/cli/commands/adapter-commands-s2a.js +1 -1
  8. package/dist/cli/commands/asset-commands.js +33 -16
  9. package/dist/cli/commands/audit-commands.js +1 -1
  10. package/dist/cli/commands/bee-commands.js +1 -1
  11. package/dist/cli/commands/capability-commands.js +1 -1
  12. package/dist/cli/commands/classify-classify-commands.js +1 -1
  13. package/dist/cli/commands/code-commands.js +1 -1
  14. package/dist/cli/commands/code-review-commands.js +1 -1
  15. package/dist/cli/commands/codegraph-commands.js +1 -1
  16. package/dist/cli/commands/compact-command.js +1 -1
  17. package/dist/cli/commands/complexity-commands.js +1 -1
  18. package/dist/cli/commands/config-commands.js +1 -1
  19. package/dist/cli/commands/context-commands.js +1 -1
  20. package/dist/cli/commands/contract-commands.js +1 -1
  21. package/dist/cli/commands/core/artifacts-command.js +1 -1
  22. package/dist/cli/commands/core/binding-commands.js +1 -1
  23. package/dist/cli/commands/core/doctor-command.js +63 -3
  24. package/dist/cli/commands/core/memory-command.js +1 -1
  25. package/dist/cli/commands/core/profile-command.js +1 -1
  26. package/dist/cli/commands/core/proxy-command.js +1 -1
  27. package/dist/cli/commands/core/session-command.js +1 -1
  28. package/dist/cli/commands/core/skill-command.js +2 -2
  29. package/dist/cli/commands/core/standards-command.js +1 -1
  30. package/dist/cli/commands/dispatch-commands.js +2 -2
  31. package/dist/cli/commands/dispatch-from-dag.js +1 -1
  32. package/dist/cli/commands/doc-commands.js +1 -1
  33. package/dist/cli/commands/ecc-commands.js +2 -2
  34. package/dist/cli/commands/evolution-commands.js +1 -1
  35. package/dist/cli/commands/feedback-commands.js +1 -1
  36. package/dist/cli/commands/final-review-commands.js +1 -1
  37. package/dist/cli/commands/fixture-commands.js +1 -1
  38. package/dist/cli/commands/fork-commands.js +1 -1
  39. package/dist/cli/commands/gate-commands.js +1 -1
  40. package/dist/cli/commands/heartbeat-commands.js +1 -1
  41. package/dist/cli/commands/hook-handle.js +1 -1
  42. package/dist/cli/commands/hooks-commands.js +1 -1
  43. package/dist/cli/commands/ide-commands.js +1 -1
  44. package/dist/cli/commands/impact-commands.js +1 -1
  45. package/dist/cli/commands/job-commands.js +1 -1
  46. package/dist/cli/commands/legacy-commands.js +1 -1
  47. package/dist/cli/commands/log-commands.js +1 -1
  48. package/dist/cli/commands/loop-commands.js +1 -1
  49. package/dist/cli/commands/loop-eval-commands.js +1 -1
  50. package/dist/cli/commands/memory-commands.js +1 -1
  51. package/dist/cli/commands/migrate-1-4-1-command.js +1 -1
  52. package/dist/cli/commands/migrate-v2-10-to-v2-11-command.js +1 -1
  53. package/dist/cli/commands/mut-commands.d.ts +1 -1
  54. package/dist/cli/commands/mut-commands.js +1 -5
  55. package/dist/cli/commands/observability-commands.js +1 -1
  56. package/dist/cli/commands/openspec-commands.js +2 -2
  57. package/dist/cli/commands/perf-audit-commands.js +2 -2
  58. package/dist/cli/commands/perf-commands.js +1 -1
  59. package/dist/cli/commands/polyrepo-commands.js +1 -1
  60. package/dist/cli/commands/prd-blocks-commands.js +1 -1
  61. package/dist/cli/commands/prd-commands.js +1 -1
  62. package/dist/cli/commands/project-commands.js +1 -1
  63. package/dist/cli/commands/qa-business-review-commands.js +1 -1
  64. package/dist/cli/commands/qa-commands.d.ts +1 -1
  65. package/dist/cli/commands/qa-commands.js +2 -2
  66. package/dist/cli/commands/release-commands.js +1 -1
  67. package/dist/cli/commands/request-commands.js +1 -1
  68. package/dist/cli/commands/retrospective-commands.js +1 -1
  69. package/dist/cli/commands/reviewer-commands.js +1 -1
  70. package/dist/cli/commands/role-commands.js +1 -1
  71. package/dist/cli/commands/runtime-commands.js +1 -2
  72. package/dist/cli/commands/sc-commands.js +1 -1
  73. package/dist/cli/commands/scan-commands.js +1 -1
  74. package/dist/cli/commands/security-audit-commands.js +2 -2
  75. package/dist/cli/commands/share-commands.d.ts +19 -0
  76. package/dist/cli/commands/share-commands.js +121 -2
  77. package/dist/cli/commands/skill-conformance-commands.js +1 -1
  78. package/dist/cli/commands/skill-loop-engineering-readiness-commands.js +1 -1
  79. package/dist/cli/commands/skill-search-commands.js +1 -1
  80. package/dist/cli/commands/slice-commands.js +1 -1
  81. package/dist/cli/commands/slice-integrate-commands.js +1 -1
  82. package/dist/cli/commands/slice-review-commands.js +1 -1
  83. package/dist/cli/commands/smoke-commands.js +1 -1
  84. package/dist/cli/commands/sop-commands.js +1 -1
  85. package/dist/cli/commands/statusline-commands.js +1 -1
  86. package/dist/cli/commands/sub-agent-commands.js +2 -1
  87. package/dist/cli/commands/understand-commands.js +1 -1
  88. package/dist/cli/commands/upgrade-commands.js +1 -1
  89. package/dist/cli/commands/user-touchpoint-commands.js +1 -1
  90. package/dist/cli/commands/verdict-aggregate-command.js +1 -1
  91. package/dist/cli/commands/workflow-commands.js +1 -1
  92. package/dist/cli/commands/workflow-plan-commands.js +1 -1
  93. package/dist/cli/commands/workspace/init-command.js +1 -1
  94. package/dist/cli/commands/workspace/migrate-command.js +1 -1
  95. package/dist/cli/commands/workspace/reconcile-command.js +1 -1
  96. package/dist/cli/index.js +1 -1
  97. package/dist/cli/program.js +2 -2
  98. package/dist/services/artifacts/artifact-prerequisites.js +1 -1
  99. package/dist/services/artifacts/request-artifact-service.js +1 -1
  100. package/dist/services/artifacts/workspace-service.js +1 -1
  101. package/dist/services/code/auto-compact-orchestrator.js +5 -2
  102. package/dist/services/code-review/ocr-service.d.ts +1 -1
  103. package/dist/services/code-review/ocr-service.js +6 -3
  104. package/dist/services/config/config-types.js +1 -1
  105. package/dist/services/dashboard/project-dashboard-service.js +2 -2
  106. package/dist/services/dispatch/batch-counter.js +1 -1
  107. package/dist/services/dispatch/dispatch-record-writer.js +1 -1
  108. package/dist/services/job/job-progress-store.d.ts +2 -2
  109. package/dist/services/job/job-types.d.ts +27 -27
  110. package/dist/services/log/retention.js +1 -1
  111. package/dist/services/openspec/openspec-archive-service.js +1 -1
  112. package/dist/services/openspec/openspec-bridge-service.js +1 -1
  113. package/dist/services/openspec/openspec-init-service.js +1 -1
  114. package/dist/services/openspec/openspec-propose-from-doctor-service.js +1 -1
  115. package/dist/services/openspec/openspec-render-service.js +1 -1
  116. package/dist/services/openspec/openspec-scan-service.js +1 -1
  117. package/dist/services/openspec/openspec-validate-service.js +1 -1
  118. package/dist/services/scan/acceptance-coverage-service.js +1 -1
  119. package/dist/services/scan/archetype-service.js +1 -1
  120. package/dist/services/scan/existing-system-service.js +1 -1
  121. package/dist/services/scan/libraries-service.js +1 -1
  122. package/dist/services/skill/skill-search-service.d.ts +5 -5
  123. package/dist/services/skill/skill-search-service.js +2 -2
  124. package/dist/services/skills/skill-registry.js +2 -2
  125. package/dist/services/skills/skill-runbook-service.js +1 -1
  126. package/dist/services/slice/slice-check-service.js +1 -1
  127. package/dist/services/understand/understand-hybrid-service.js +1 -1
  128. package/dist/services/understand/understand-scan-service.js +2 -2
  129. package/dist/services/upgrade/upgrade-service.js +4 -3
  130. package/dist/services/verdict/envelopes.d.ts +2 -2
  131. package/dist/services/verdict/envelopes.js +2 -2
  132. package/dist/services/verdict/verdict-aggregator.d.ts +3 -3
  133. package/dist/services/workflow/autonomous-resume-writer.js +1 -1
  134. package/dist/services/workflow/plan-reader.d.ts +1 -1
  135. package/dist/services/workflow/plan-reader.js +1 -1
  136. package/dist/services/workflow/plan-refresher.d.ts +1 -1
  137. package/dist/services/workflow/plan-refresher.js +1 -1
  138. package/dist/services/workflow/plan-trigger-detector.d.ts +1 -1
  139. package/dist/services/workflow/plan-trigger-detector.js +1 -1
  140. package/dist/services/workflow/workflow-router-service.js +11 -9
  141. package/dist/services/workspace/migrate-service.js +1 -1
  142. package/dist/services/workspace/workspace-service.js +1 -1
  143. package/package.json +13 -8
  144. package/scripts/sync-version.mjs +8 -1
  145. package/skills/bee/peaks-rd/SKILL.md +14 -1
  146. package/skills/peaks-code/SKILL.md +44 -0
  147. package/skills/peaks-code/references/sub-agent-dispatch.md +30 -0
  148. package/dist/services/agent/ecc-cache-service.d.ts +0 -97
  149. package/dist/services/agent/ecc-cache-service.js +0 -491
  150. package/dist/services/audit-independent/perf-audit-service.d.ts +0 -151
  151. package/dist/services/audit-independent/perf-audit-service.js +0 -316
  152. package/dist/services/audit-independent/security-audit-service.d.ts +0 -155
  153. package/dist/services/audit-independent/security-audit-service.js +0 -316
  154. package/dist/services/context/dispatch-context-guard.d.ts +0 -27
  155. package/dist/services/context/dispatch-context-guard.js +0 -192
  156. package/dist/services/context/shared-channel.d.ts +0 -93
  157. package/dist/services/context/shared-channel.js +0 -308
  158. package/dist/services/crystallization/crystallization-service.d.ts +0 -239
  159. package/dist/services/crystallization/crystallization-service.js +0 -397
  160. package/dist/services/crystallization/crystallization-store.d.ts +0 -75
  161. package/dist/services/crystallization/crystallization-store.js +0 -193
  162. package/dist/services/crystallization/crystallization-types.d.ts +0 -313
  163. package/dist/services/crystallization/crystallization-types.js +0 -299
  164. package/dist/services/crystallization/evidence-brief-builder.d.ts +0 -237
  165. package/dist/services/crystallization/evidence-brief-builder.js +0 -240
  166. package/dist/services/doctor/doctor-service.d.ts +0 -149
  167. package/dist/services/doctor/doctor-service.js +0 -929
  168. package/dist/services/filesystem/file-lock.d.ts +0 -11
  169. package/dist/services/filesystem/file-lock.js +0 -152
  170. package/dist/services/final-review/final-review-service.d.ts +0 -12
  171. package/dist/services/final-review/final-review-service.js +0 -52
  172. package/dist/services/final-review/final-review-types.d.ts +0 -42
  173. package/dist/services/final-review/final-review-types.js +0 -18
  174. package/dist/services/job/job-resource-snapshot.d.ts +0 -7
  175. package/dist/services/job/job-resource-snapshot.js +0 -75
  176. package/dist/services/mut/assert-scanner.d.ts +0 -6
  177. package/dist/services/mut/assert-scanner.js +0 -116
  178. package/dist/services/mut/index.d.ts +0 -8
  179. package/dist/services/mut/index.js +0 -7
  180. package/dist/services/mut/mut-runner.d.ts +0 -39
  181. package/dist/services/mut/mut-runner.js +0 -29
  182. package/dist/services/mut/production-stryker.d.ts +0 -2
  183. package/dist/services/mut/production-stryker.js +0 -75
  184. package/dist/services/mut/report-builder.d.ts +0 -11
  185. package/dist/services/mut/report-builder.js +0 -110
  186. package/dist/services/mut/report-loader.d.ts +0 -31
  187. package/dist/services/mut/report-loader.js +0 -78
  188. package/dist/services/mut/thresholds.d.ts +0 -21
  189. package/dist/services/mut/thresholds.js +0 -22
  190. package/dist/services/mut/types.d.ts +0 -336
  191. package/dist/services/mut/types.js +0 -59
  192. package/dist/shared/fs.d.ts +0 -4
  193. package/dist/shared/fs.js +0 -26
  194. package/dist/shared/paths.d.ts +0 -6
  195. package/dist/shared/paths.js +0 -50
  196. package/dist/shared/result.d.ts +0 -22
  197. package/dist/shared/result.js +0 -46
  198. package/dist/shared/version.d.ts +0 -1
  199. package/dist/shared/version.js +0 -1
  200. package/examples/video-demo/README.md +0 -142
  201. package/examples/video-demo/out/en-closing-960.png +0 -0
  202. package/examples/video-demo/out/zh-closing-960.png +0 -0
  203. package/examples/video-demo/preview/peaks-loop-demo-en.mp4 +0 -0
  204. package/examples/video-demo/preview/peaks-loop-demo.mp4 +0 -0
@@ -1,308 +0,0 @@
1
- /**
2
- * G8 — cross sub-agent shared channel (RL-23..RL-26, AC-47..AC-49).
3
- *
4
- * Dispatcher-mediated indirect signal: sub-agent A writes a shared entry,
5
- * the dispatcher stores it in a per-batch JSON file, sub-agent B (still
6
- * in flight) reads it. A and B never directly talk. This is the
7
- * pseudo-swarm property 3 upgrade; it is NOT peer-to-peer messaging.
8
- *
9
- * Path convention (G8.3):
10
- * `.peaks/_sub_agents/<sid>/shared/<rid>-<batchId>.json`
11
- *
12
- * The file is atomic-write (tmp + rename). Last-write-wins by key. Value
13
- * size limit: ≤ 1KB soft warn, ≥ 64KB hard reject. File size cap: 1MB
14
- * with LRU eviction.
15
- *
16
- * See: `.peaks/memory/sub-agent-shared-channel-cross-completion.md` for
17
- * the full G8 rule.
18
- */
19
- import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
20
- import { dirname } from 'node:path';
21
- import { assertSafeSharedChannelPath, sharedChannelPath } from './dispatch-context-guard.js';
22
- import { withFileLockSync } from '../filesystem/file-lock.js';
23
- export const SHARED_CHANNEL_MAX_VALUE_BYTES = 64 * 1024; // 64KB hard reject
24
- export const SHARED_CHANNEL_SOFT_VALUE_WARN = 1024; // 1KB soft warning
25
- export const SHARED_CHANNEL_MAX_FILE_BYTES = 1024 * 1024; // 1MB LRU cap
26
- export const SHARED_CHANNEL_TTL_DAYS = 30; // 30-day TTL on orphan channels
27
- /**
28
- * Write a shared entry to the per-batch channel file. Atomic-write
29
- * (tmp + rename). Returns the new entry + channel size + last-write-wins
30
- * flag (true if the key already existed; the new value overwrites).
31
- *
32
- * RL-25 size limit: `value ≥ 64KB` is hard-rejected with `VALUE_TOO_LARGE`.
33
- * `value > 1KB and < 64KB` is a soft warning (returned in the result).
34
- */
35
- export function writeSharedEntry(opts) {
36
- if (typeof opts.key !== 'string' || opts.key.length === 0) {
37
- return { ok: false, code: 'INVALID_BATCH_ID', message: 'key must be non-empty' };
38
- }
39
- if (typeof opts.from !== 'string' || opts.from.length === 0) {
40
- return { ok: false, code: 'INVALID_BATCH_ID', message: 'from must be non-empty' };
41
- }
42
- if (opts.value === null || typeof opts.value !== 'object' || Array.isArray(opts.value)) {
43
- return {
44
- ok: false,
45
- code: 'INVALID_BATCH_ID',
46
- message: 'value must be a JSON object (not array, not primitive)'
47
- };
48
- }
49
- const valueSize = Buffer.byteLength(JSON.stringify(opts.value), 'utf8');
50
- if (valueSize >= SHARED_CHANNEL_MAX_VALUE_BYTES) {
51
- return {
52
- ok: false,
53
- code: 'VALUE_TOO_LARGE',
54
- message: `value size ${valueSize} bytes exceeds limit ${SHARED_CHANNEL_MAX_VALUE_BYTES} bytes (RL-25)`
55
- };
56
- }
57
- const softWarning = valueSize > SHARED_CHANNEL_SOFT_VALUE_WARN;
58
- const channelFile = sharedChannelPath(opts.projectRoot, opts.sid, opts.rid, opts.batchId);
59
- assertSafeSharedChannelPath(channelFile, opts.projectRoot);
60
- // Slice 2026-06-23-audit-3rd #2: the read-modify-write sequence below
61
- // is wrapped in withFileLockSync to prevent lost updates from two
62
- // concurrent `peaks sub-agent share` calls landing on the same batch.
63
- // Without the lock, the second writer's read sees a stale channel,
64
- // and its renameSync silently overwrites the first writer's entry.
65
- return withFileLockSync(channelFile, () => {
66
- let channel;
67
- try {
68
- channel = readChannelOrEmpty(channelFile, opts.batchId);
69
- }
70
- catch (err) {
71
- return {
72
- ok: false,
73
- code: 'WRITE_ERROR',
74
- message: `failed to read existing channel: ${err.message}`
75
- };
76
- }
77
- const lastWriteWins = Object.prototype.hasOwnProperty.call(channel.entries, opts.key);
78
- // LRU eviction: if writing would push the file over the 1MB cap,
79
- // evict oldest entries until the new write fits.
80
- const entry = {
81
- version: 1,
82
- at: new Date().toISOString(),
83
- from: opts.from,
84
- key: opts.key,
85
- value: Object.freeze({ ...opts.value }),
86
- valueSize
87
- };
88
- const projectedChannel = {
89
- ...channel,
90
- updatedAt: entry.at,
91
- entries: { ...channel.entries, [opts.key]: entry }
92
- };
93
- const projectedSize = JSON.stringify(projectedChannel).length;
94
- let lruEvicted = 0;
95
- if (projectedSize > SHARED_CHANNEL_MAX_FILE_BYTES) {
96
- const sortedKeys = Object.keys(projectedChannel.entries).sort((a, b) => {
97
- const ea = projectedChannel.entries[a];
98
- const eb = projectedChannel.entries[b];
99
- if (!ea || !eb)
100
- return 0;
101
- return ea.at.localeCompare(eb.at);
102
- });
103
- let working = projectedChannel;
104
- while (JSON.stringify(working).length > SHARED_CHANNEL_MAX_FILE_BYTES &&
105
- sortedKeys.length > 0) {
106
- const oldestKey = sortedKeys.shift();
107
- if (oldestKey === opts.key) {
108
- // Don't evict the entry we just wrote.
109
- break;
110
- }
111
- const nextEntries = {};
112
- for (const [k, v] of Object.entries(working.entries)) {
113
- if (k !== oldestKey) {
114
- nextEntries[k] = v;
115
- }
116
- }
117
- working = { ...working, entries: nextEntries };
118
- lruEvicted += 1;
119
- }
120
- try {
121
- writeAtomic(channelFile, working);
122
- }
123
- catch (err) {
124
- return {
125
- ok: false,
126
- code: 'WRITE_ERROR',
127
- message: `failed to write channel after LRU eviction: ${err.message}`
128
- };
129
- }
130
- return {
131
- ok: true,
132
- entry,
133
- channelSize: JSON.stringify(working).length,
134
- lastWriteWins,
135
- softWarning
136
- };
137
- }
138
- try {
139
- writeAtomic(channelFile, projectedChannel);
140
- }
141
- catch (err) {
142
- return {
143
- ok: false,
144
- code: 'WRITE_ERROR',
145
- message: `failed to write channel: ${err.message}`
146
- };
147
- }
148
- // lruEvicted is 0 here; expose the count through the size of the
149
- // returned channel for caller diagnostics.
150
- void lruEvicted;
151
- return {
152
- ok: true,
153
- entry,
154
- channelSize: projectedSize,
155
- lastWriteWins,
156
- softWarning
157
- };
158
- });
159
- }
160
- /**
161
- * Read the shared channel for a batch. Returns the channel with all
162
- * matching entries. Filters: `--since` (ISO8601) and `--key` (glob
163
- * pattern; simple `*` wildcard, no regex).
164
- *
165
- * If the channel file does not exist, returns an empty channel
166
- * (this is the dispatcher's "fresh batch" view).
167
- */
168
- export function readSharedChannel(opts) {
169
- const channelFile = sharedChannelPath(opts.projectRoot, opts.sid, opts.rid, opts.batchId);
170
- assertSafeSharedChannelPath(channelFile, opts.projectRoot);
171
- const channel = readChannelOrEmpty(channelFile, opts.batchId);
172
- const since = opts.since;
173
- const pattern = opts.keyPattern;
174
- if (!since && !pattern) {
175
- return channel;
176
- }
177
- const filteredEntries = {};
178
- const matcher = pattern ? compileKeyPattern(pattern) : null;
179
- for (const [k, v] of Object.entries(channel.entries)) {
180
- if (since && v.at < since)
181
- continue;
182
- if (matcher && !matcher(k))
183
- continue;
184
- filteredEntries[k] = v;
185
- }
186
- return { ...channel, entries: filteredEntries };
187
- }
188
- /**
189
- * Garbage-collect a single channel. Returns true if the file was
190
- * deleted, false if it did not exist.
191
- */
192
- export function gcChannel(opts) {
193
- const channelFile = sharedChannelPath(opts.projectRoot, opts.sid, opts.rid, opts.batchId);
194
- if (!existsSync(channelFile)) {
195
- return false;
196
- }
197
- // Best-effort delete; R-2 guard not strictly needed (we built the path)
198
- // but kept for safety.
199
- assertSafeSharedChannelPath(channelFile, opts.projectRoot);
200
- try {
201
- unlinkSync(channelFile);
202
- return true;
203
- }
204
- catch {
205
- return false;
206
- }
207
- }
208
- /**
209
- * Check if a channel file is older than `SHARED_CHANNEL_TTL_DAYS` days
210
- * and should be GC'd as an orphan. Returns true if file is missing
211
- * (already GC'd) or older than TTL.
212
- */
213
- export function isOrphanChannel(opts) {
214
- const channelFile = sharedChannelPath(opts.projectRoot, opts.sid, opts.rid, opts.batchId);
215
- if (!existsSync(channelFile)) {
216
- return true;
217
- }
218
- const s = statSync(channelFile);
219
- const now = opts.now ?? new Date();
220
- const ageMs = now.getTime() - s.mtimeMs;
221
- const ttlMs = SHARED_CHANNEL_TTL_DAYS * 24 * 60 * 60 * 1000;
222
- return ageMs > ttlMs;
223
- }
224
- // ─── internals ───────────────────────────────────────────────────────
225
- function readChannelOrEmpty(channelFile, batchId) {
226
- if (!existsSync(channelFile)) {
227
- const now = new Date().toISOString();
228
- return { batchId, createdAt: now, updatedAt: now, entries: {} };
229
- }
230
- let parsed;
231
- try {
232
- parsed = JSON.parse(readFileSync(channelFile, 'utf8'));
233
- }
234
- catch {
235
- const now = new Date().toISOString();
236
- return { batchId, createdAt: now, updatedAt: now, entries: {} };
237
- }
238
- if (!isObject(parsed)) {
239
- const now = new Date().toISOString();
240
- return { batchId, createdAt: now, updatedAt: now, entries: {} };
241
- }
242
- const obj = parsed;
243
- const batchIdField = typeof obj.batchId === 'string' ? obj.batchId : batchId;
244
- const createdAt = typeof obj.createdAt === 'string' ? obj.createdAt : new Date().toISOString();
245
- const updatedAt = typeof obj.updatedAt === 'string' ? obj.updatedAt : createdAt;
246
- const entriesField = isObject(obj.entries) ? obj.entries : {};
247
- const entries = {};
248
- for (const [k, v] of Object.entries(entriesField)) {
249
- if (isValidEntry(v)) {
250
- entries[k] = v;
251
- }
252
- }
253
- return { batchId: batchIdField, createdAt, updatedAt, entries };
254
- }
255
- function isValidEntry(v) {
256
- if (!isObject(v))
257
- return false;
258
- // Slice 2026-06-23-audit-4th #E2: pre-versioning records (no
259
- // `version` field) are still accepted on read for backward compat.
260
- // The writer stamps `version: 1` going forward; readers default
261
- // missing/legacy entries to version 1 in memory.
262
- if (!('version' in v) || v.version === 1) {
263
- return (typeof v.at === 'string' &&
264
- typeof v.from === 'string' &&
265
- typeof v.key === 'string' &&
266
- isObject(v.value) &&
267
- typeof v.valueSize === 'number');
268
- }
269
- // Future versions: read-side handler can branch on `v.version` once
270
- // v2 lands. For now, anything other than 1 is rejected.
271
- return false;
272
- }
273
- function isObject(v) {
274
- return typeof v === 'object' && v !== null && !Array.isArray(v);
275
- }
276
- function writeAtomic(path, channel) {
277
- const dir = dirname(path);
278
- // Slice 2026-06-23-audit-3rd #11: skip mkdirSync when the dir already
279
- // exists. The `recursive: true` mkdir is a syscall (~50µs on macOS,
280
- // ~10ms on cold Windows cache); the `existsSync` short-circuit saves
281
- // it on the hot path (every `peaks sub-agent share` + every
282
- // `peaks sub-agent heartbeat`).
283
- if (!existsSync(dir)) {
284
- mkdirSync(dir, { recursive: true });
285
- }
286
- const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
287
- writeFileSync(tmp, JSON.stringify(channel, null, 2) + '\n', 'utf8');
288
- renameSync(tmp, path);
289
- }
290
- /**
291
- * Compile a simple key pattern with `*` wildcards to a matcher. Only
292
- * `*` is special; everything else is a literal. `*` matches zero or
293
- * more characters. Examples:
294
- * "rd.*" matches "rd.completed", "rd.found-blocker"
295
- * "*.completed" matches "rd.completed", "qa.completed"
296
- * "*" matches everything
297
- */
298
- export function compileKeyPattern(pattern) {
299
- if (pattern === '*') {
300
- return () => true;
301
- }
302
- const escaped = pattern
303
- .split('*')
304
- .map((part) => part.replace(/[.+?^${}()|[\]\\]/g, '\\$&'))
305
- .join('.*');
306
- const re = new RegExp(`^${escaped}$`);
307
- return (key) => re.test(key);
308
- }
@@ -1,239 +0,0 @@
1
- import type Database from "better-sqlite3";
2
- import { z } from "zod";
3
- import { type CrystallizationEvent, type CrystallizationEventInput, type CrystallizationEventStatus, type EvidenceBrief } from "./crystallization-types.js";
4
- import { listCrystallizationEvents } from "./crystallization-store.js";
5
- import { type LoopReleaseInput } from "../loop/loop-release-types.js";
6
- /**
7
- * CrystallizationService — spec §5 (post-run crystallization)
8
- * + §4.5 + §4.7 + §10 RL-2 / RL-3 / RL-7.
9
- *
10
- * Hard rules enforced at THIS layer (defense in depth; the Zod
11
- * schemas above and the brief refine guard add the second wall):
12
- *
13
- * - AC-4 / RL-2: a crystallization_event is written only when the
14
- * candidate task has `task_status === 'completed'` AND
15
- * `gates_passed === true` AND `evidence_collected === true`.
16
- * Any pre-run attempt (status !== 'completed') is rejected
17
- * with `CRYSTALLIZATION_PRE_RUN`. This is the pre-run block
18
- * enforced by AC-4, AC-5, AC-6, AC-15, AC-16, AC-17 of the M5
19
- * slice.
20
- * - AC-6 / RL-3: writing a (loop_release, main_bee_release,
21
- * loop_bee_relation) pair happens in a SINGLE TRANSACTION. The
22
- * brief persist on crystallization_event also lives inside
23
- * the same transaction (the brief is the durable evidence;
24
- * dropping either side would break consistency).
25
- * - AC-7 / RL-1: this layer never asks the user to hand-author
26
- * JSON / manifest / CLI verb; all inputs are typed payloads
27
- * the LLM submits on the user's behalf.
28
- * - AC-15 / RL-7: the brief is mandatory; the
29
- * `EvidenceBriefSchema.refine` guard rejects partial briefs
30
- * at the parse boundary.
31
- * - AC-16 / RL-7: the brief has all 4 sections
32
- * (what_happened / why_it_matters / what_learned /
33
- * what_action). The service throws `BriefSectionError` if the
34
- * payload fails the guard.
35
- *
36
- * Sub-domain error codes:
37
- *
38
- * - CRYSTALLIZATION_PRE_RUN: task_status / gates_passed /
39
- * evidence_collected precondition failed.
40
- * - CRYSTALLIZATION_INVALID_TASK_STATE: a task input field is
41
- * malformed (e.g. unknown task_status).
42
- * - MISSING_BRIEF_SECTION: a brief is missing at least one of
43
- * its 4 sections (raised from the evidence-brief-builder
44
- * refine; surfaced as-is by the service).
45
- * - CRYSTALLIZATION_TX_FAILED: a transaction-side effect failed
46
- * (FK constraint, duplicate key, …). The service re-raises
47
- * richer domain errors first; this is the catch-all for
48
- * unexpected SQL errors.
49
- */
50
- export declare const CRYSTALLIZATION_TASK_STATUSES: readonly ["completed", "in_progress", "scratch", "blocked", "failed"];
51
- export type CrystallizationTaskStatus = (typeof CRYSTALLIZATION_TASK_STATUSES)[number];
52
- /**
53
- * The pre-run task state the service requires. Crystallization is
54
- * GATED on these three flags. The validator rejects any input that
55
- * does not match. Adding a new flag means a schema-version bump; for
56
- * now the three are required.
57
- */
58
- export declare const CrystallizationTaskStateSchema: z.ZodObject<{
59
- task_id: z.ZodString;
60
- task_status: z.ZodLiteral<"completed">;
61
- gates_passed: z.ZodLiteral<true>;
62
- evidence_collected: z.ZodLiteral<true>;
63
- }, "strip", z.ZodTypeAny, {
64
- task_id: string;
65
- task_status: "completed";
66
- gates_passed: true;
67
- evidence_collected: true;
68
- }, {
69
- task_id: string;
70
- task_status: "completed";
71
- gates_passed: true;
72
- evidence_collected: true;
73
- }>;
74
- export type CrystallizationTaskState = z.infer<typeof CrystallizationTaskStateSchema>;
75
- export type CrystallizationIntegrityErrorCode = "CRYSTALLIZATION_PRE_RUN" | "CRYSTALLIZATION_INVALID_TASK_STATE" | "CRYSTALLIZATION_TX_FAILED" | "MISSING_BRIEF_SECTION";
76
- export declare class CrystallizationIntegrityError extends Error {
77
- readonly code: CrystallizationIntegrityErrorCode | "MISSING_BRIEF_SECTION";
78
- readonly findings: ReadonlyArray<{
79
- path: string;
80
- message: string;
81
- }>;
82
- constructor(code: CrystallizationIntegrityErrorCode, message: string, findings?: ReadonlyArray<{
83
- path: string;
84
- message: string;
85
- }>);
86
- }
87
- export interface CrystallizePayload {
88
- /** The candidate task state. Must satisfy the pre-run gate. */
89
- task: CrystallizationTaskState;
90
- /** The loop_release to create (new). Must include main bee via relation. */
91
- loop_input: LoopReleaseInput;
92
- /**
93
- * The main bee_release to create. Note: bee_release requires a
94
- * manifest row to keep the existing 4.x schema consistent;
95
- * `bee_input` here carries ONLY the manifest-level fields the
96
- * service uses. A future slice will swap in the full
97
- * retainRelease() flow.
98
- */
99
- bee_input: BeeCrystallizeInput;
100
- /**
101
- * Optional NL reason the LLM authored; surfaced on the
102
- * loop_bee_relation row (spec §4.6 `reason`).
103
- */
104
- bee_relation_reason: string;
105
- /**
106
- * The 4-section evidence brief (spec §4.7).
107
- */
108
- evidence_brief: EvidenceBrief;
109
- /** Optional structured bullets (RL-7: counts may support the brief, not replace it). */
110
- evidence_bullets?: string[];
111
- /** Source trace pointers for the brief (spec §4.5). */
112
- source_trace_pointers?: string[];
113
- /** Optional evaluator summary (independent scorers — spec §4.5). */
114
- evaluator_summary?: string;
115
- /** Optional user decision summary (spec §4.5). */
116
- user_decision_summary?: string;
117
- /** Trigger classification (spec §5.4). */
118
- trigger: CrystallizationEventInput["trigger"];
119
- }
120
- /**
121
- * Minimum bee_release payload the service needs to create a main bee
122
- * alongside a loop. The full bee_release schema (see
123
- * `src/services/skillhub/release-retain.ts`) carries manifest rows,
124
- * segment refs, file rows, etc.; M5 only writes the bee_release header
125
- * + a single bee_manifest row sufficient to keep the 4.x FK
126
- * constraints green. Full bee retaining lands in a future slice.
127
- *
128
- * Schema definition is hoisted above the interface so the typed
129
- * interface can reference the inferred Zod type without forward-
130
- * reference gymnastics.
131
- */
132
- declare const BeeNameSchema: z.ZodString;
133
- export interface BeeCrystallizeInput {
134
- bee_name: z.infer<typeof BeeNameSchema>;
135
- version: string;
136
- description: string;
137
- user_intent_raw?: string;
138
- parent_version?: string;
139
- changelog?: string;
140
- }
141
- /**
142
- * The crystallization result. The brief is returned AS PERSISTED
143
- * (re-parsed through the schema) so callers receive the canonical
144
- * shape, not the loose payload.
145
- */
146
- export interface CrystallizationResult {
147
- loop_release_id: string;
148
- bee_release_id: number;
149
- loop_bee_relation_id: number;
150
- crystallization_event_id: string;
151
- loop_release_lifecycle_status: CrystallizationEventStatus;
152
- }
153
- /**
154
- * Service interface for the post-run crystallization flow.
155
- */
156
- export declare class CrystallizationService {
157
- private readonly db;
158
- constructor(db: Database.Database);
159
- /**
160
- * Read a CrystallizationEvent by id.
161
- */
162
- read(id: string): CrystallizationEvent | undefined;
163
- /**
164
- * List events. Filter by lifecycle_status and/or the FK pointer
165
- * columns. When no filters are supplied, lists ALL events
166
- * (newest-first).
167
- */
168
- list(opts?: Parameters<typeof listCrystallizationEvents>[1]): {
169
- schema_version: "peaks.crystallization/1";
170
- id: string;
171
- lifecycle_status: "candidate" | "stable" | "retired";
172
- trigger: "user_explicit" | "llm_suggested" | "success_default_prompt" | "similar_task_recurrence";
173
- evidence_brief: {
174
- what_happened: string;
175
- why_it_matters: string;
176
- what_learned: string;
177
- what_action: string;
178
- };
179
- evidence_bullets: string[];
180
- source_trace_pointers: string[];
181
- evaluator_summary: string;
182
- user_decision_summary: string;
183
- created_at: string;
184
- created_loop_release_id?: string | undefined;
185
- updated_loop_release_id?: string | undefined;
186
- created_bee_release_id?: number | undefined;
187
- updated_bee_release_id?: number | undefined;
188
- }[];
189
- /**
190
- * Update a crystallization event's lifecycle_status. Always allowed
191
- * (no schema-version gate). Returns the updated row or undefined if
192
- * the id is absent.
193
- *
194
- * A `stable` transition is the natural post-crystallization state;
195
- * the service does NOT enforce user-confirmation here (per spec
196
- * §5.6 promotion happens on a separate `peaks loop promote` path).
197
- * `retired` is the dispose path (`peaks asset dispose`).
198
- */
199
- updateStatus(id: string, next: CrystallizationEventStatus): CrystallizationEvent | undefined;
200
- /**
201
- * Pre-run gate: throws if any of `task_status`, `gates_passed`,
202
- * `evidence_collected` is not as required. Called from
203
- * `crystallize` BEFORE the transaction begins.
204
- *
205
- * Reachable as a public method so the CLI / tests can probe the
206
- * gate in isolation.
207
- */
208
- assertReady(task: CrystallizationTaskState): CrystallizationTaskState;
209
- /**
210
- * Run the post-run crystallization flow.
211
- *
212
- * Steps (all in ONE better-sqlite3 transaction, atomic):
213
- *
214
- * 1. Pre-run gate (task must be completed / gates_passed /
215
- * evidence_collected).
216
- * 2. Re-validate the 4-section brief (EvidenceBriefSchema.parse,
217
- * EvidenceBriefSchema.refine).
218
- * 3. Insert loop_release.
219
- * 4. Insert bee_release header (+ a single bee_manifest row to
220
- * keep the 4.x FK contract).
221
- * 5. Insert loop_bee_relation row with role='main'.
222
- * 6. Insert crystallization_event with the brief persisted.
223
- *
224
- * Returns the four ids + the new loop's lifecycle_status. On any
225
- * failure the transaction is rolled back and the partial rows
226
- * disappear.
227
- *
228
- * Throws:
229
- * - CrystallizationIntegrityError(CRYSTALLIZATION_PRE_RUN) on
230
- * the pre-run gate.
231
- * - BriefSectionError or ZodError if the brief is malformed;
232
- * surfaced as
233
- * CrystallizationIntegrityError(MISSING_BRIEF_SECTION).
234
- * - CrystallizationIntegrityError(CRYSTALLIZATION_TX_FAILED)
235
- * on any SQL transaction failure.
236
- */
237
- crystallize(payload: CrystallizePayload): CrystallizationResult;
238
- }
239
- export {};