peaks-loop 4.0.0-beta.12 → 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.
- package/CHANGELOG.md +282 -147
- package/README-en.md +1 -1
- package/README.md +32 -1
- package/bin/peaks.js +0 -0
- package/dist/cli/cli-helpers.d.ts +1 -1
- package/dist/cli/cli-helpers.js +1 -1
- package/dist/cli/commands/adapter-commands-s2a.js +1 -1
- package/dist/cli/commands/asset-commands.js +33 -16
- package/dist/cli/commands/audit-commands.js +1 -1
- package/dist/cli/commands/bee-commands.js +1 -1
- package/dist/cli/commands/capability-commands.js +1 -1
- package/dist/cli/commands/classify-classify-commands.js +1 -1
- package/dist/cli/commands/code-commands.js +1 -1
- package/dist/cli/commands/code-review-commands.js +1 -1
- package/dist/cli/commands/codegraph-commands.js +1 -1
- package/dist/cli/commands/compact-command.js +1 -1
- package/dist/cli/commands/complexity-commands.js +1 -1
- package/dist/cli/commands/config-commands.js +1 -1
- package/dist/cli/commands/context-commands.js +1 -1
- package/dist/cli/commands/contract-commands.js +1 -1
- package/dist/cli/commands/core/artifacts-command.js +1 -1
- package/dist/cli/commands/core/binding-commands.js +1 -1
- package/dist/cli/commands/core/doctor-command.js +63 -3
- package/dist/cli/commands/core/memory-command.js +1 -1
- package/dist/cli/commands/core/profile-command.js +1 -1
- package/dist/cli/commands/core/proxy-command.js +1 -1
- package/dist/cli/commands/core/session-command.js +1 -1
- package/dist/cli/commands/core/skill-command.js +2 -2
- package/dist/cli/commands/core/standards-command.js +1 -1
- package/dist/cli/commands/dispatch-commands.js +2 -2
- package/dist/cli/commands/dispatch-from-dag.js +1 -1
- package/dist/cli/commands/doc-commands.js +1 -1
- package/dist/cli/commands/ecc-commands.js +2 -2
- package/dist/cli/commands/evolution-commands.js +1 -1
- package/dist/cli/commands/feedback-commands.js +1 -1
- package/dist/cli/commands/final-review-commands.js +1 -1
- package/dist/cli/commands/fixture-commands.js +1 -1
- package/dist/cli/commands/fork-commands.js +1 -1
- package/dist/cli/commands/gate-commands.js +1 -1
- package/dist/cli/commands/heartbeat-commands.js +1 -1
- package/dist/cli/commands/hook-handle.js +1 -1
- package/dist/cli/commands/hooks-commands.js +1 -1
- package/dist/cli/commands/ide-commands.js +1 -1
- package/dist/cli/commands/impact-commands.js +1 -1
- package/dist/cli/commands/job-commands.js +1 -1
- package/dist/cli/commands/legacy-commands.js +1 -1
- package/dist/cli/commands/log-commands.js +1 -1
- package/dist/cli/commands/loop-commands.js +1 -1
- package/dist/cli/commands/loop-eval-commands.js +1 -1
- package/dist/cli/commands/memory-commands.js +1 -1
- package/dist/cli/commands/migrate-1-4-1-command.js +1 -1
- package/dist/cli/commands/migrate-v2-10-to-v2-11-command.js +1 -1
- package/dist/cli/commands/mut-commands.d.ts +1 -1
- package/dist/cli/commands/mut-commands.js +1 -5
- package/dist/cli/commands/observability-commands.js +1 -1
- package/dist/cli/commands/openspec-commands.js +2 -2
- package/dist/cli/commands/perf-audit-commands.js +2 -2
- package/dist/cli/commands/perf-commands.js +1 -1
- package/dist/cli/commands/polyrepo-commands.js +1 -1
- package/dist/cli/commands/prd-blocks-commands.js +1 -1
- package/dist/cli/commands/prd-commands.js +1 -1
- package/dist/cli/commands/project-commands.js +1 -1
- package/dist/cli/commands/qa-business-review-commands.js +1 -1
- package/dist/cli/commands/qa-commands.d.ts +1 -1
- package/dist/cli/commands/qa-commands.js +2 -2
- package/dist/cli/commands/release-commands.js +1 -1
- package/dist/cli/commands/request-commands.js +1 -1
- package/dist/cli/commands/retrospective-commands.js +1 -1
- package/dist/cli/commands/reviewer-commands.js +1 -1
- package/dist/cli/commands/role-commands.js +1 -1
- package/dist/cli/commands/runtime-commands.js +1 -2
- package/dist/cli/commands/sc-commands.js +1 -1
- package/dist/cli/commands/scan-commands.js +1 -1
- package/dist/cli/commands/security-audit-commands.js +2 -2
- package/dist/cli/commands/sediment-commands.js +10 -1
- package/dist/cli/commands/share-commands.d.ts +19 -0
- package/dist/cli/commands/share-commands.js +121 -2
- package/dist/cli/commands/skill-conformance-commands.js +1 -1
- package/dist/cli/commands/skill-loop-engineering-readiness-commands.js +1 -1
- package/dist/cli/commands/skill-search-commands.js +1 -1
- package/dist/cli/commands/slice-commands.js +1 -1
- package/dist/cli/commands/slice-integrate-commands.js +1 -1
- package/dist/cli/commands/slice-review-commands.js +1 -1
- package/dist/cli/commands/smoke-commands.js +1 -1
- package/dist/cli/commands/sop-commands.js +1 -1
- package/dist/cli/commands/statusline-commands.js +1 -1
- package/dist/cli/commands/sub-agent-commands.js +2 -1
- package/dist/cli/commands/understand-commands.js +1 -1
- package/dist/cli/commands/upgrade-commands.js +1 -1
- package/dist/cli/commands/user-touchpoint-commands.js +1 -1
- package/dist/cli/commands/verdict-aggregate-command.js +1 -1
- package/dist/cli/commands/workflow-commands.js +1 -1
- package/dist/cli/commands/workflow-plan-commands.js +1 -1
- package/dist/cli/commands/workspace/init-command.js +1 -1
- package/dist/cli/commands/workspace/migrate-command.js +1 -1
- package/dist/cli/commands/workspace/reconcile-command.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/program.js +2 -2
- package/dist/services/artifacts/artifact-prerequisites.js +1 -1
- package/dist/services/artifacts/request-artifact-service.js +1 -1
- package/dist/services/artifacts/workspace-service.js +1 -1
- package/dist/services/code/auto-compact-orchestrator.js +5 -2
- package/dist/services/code-review/ocr-service.d.ts +1 -1
- package/dist/services/code-review/ocr-service.js +6 -3
- package/dist/services/config/config-types.js +1 -1
- package/dist/services/dashboard/project-dashboard-service.js +2 -2
- package/dist/services/dispatch/batch-counter.js +1 -1
- package/dist/services/dispatch/dispatch-record-writer.js +1 -1
- package/dist/services/job/job-progress-store.d.ts +2 -2
- package/dist/services/job/job-types.d.ts +27 -27
- package/dist/services/log/retention.js +1 -1
- package/dist/services/openspec/openspec-archive-service.js +1 -1
- package/dist/services/openspec/openspec-bridge-service.js +1 -1
- package/dist/services/openspec/openspec-init-service.js +1 -1
- package/dist/services/openspec/openspec-propose-from-doctor-service.js +1 -1
- package/dist/services/openspec/openspec-render-service.js +1 -1
- package/dist/services/openspec/openspec-scan-service.js +1 -1
- package/dist/services/openspec/openspec-validate-service.js +1 -1
- package/dist/services/scan/acceptance-coverage-service.js +1 -1
- package/dist/services/scan/archetype-service.js +1 -1
- package/dist/services/scan/existing-system-service.js +1 -1
- package/dist/services/scan/libraries-service.js +1 -1
- package/dist/services/skill/skill-search-service.d.ts +5 -5
- package/dist/services/skill/skill-search-service.js +2 -2
- package/dist/services/skillhub/migrations/001-initial.sql +71 -0
- package/dist/services/skillhub/migrations/002-loop-release.sql +32 -0
- package/dist/services/skillhub/migrations/003-loop-bee-relation.sql +35 -0
- package/dist/services/skillhub/migrations/004-loop-bee-extension.sql +65 -0
- package/dist/services/skillhub/migrations/005-evolution-evaluation.sql +81 -0
- package/dist/services/skillhub/migrations/006-crystallization-event.sql +92 -0
- package/dist/services/skills/skill-registry.js +2 -2
- package/dist/services/skills/skill-runbook-service.js +1 -1
- package/dist/services/slice/slice-check-service.js +1 -1
- package/dist/services/understand/understand-hybrid-service.js +1 -1
- package/dist/services/understand/understand-scan-service.js +2 -2
- package/dist/services/upgrade/upgrade-service.js +4 -3
- package/dist/services/verdict/envelopes.d.ts +2 -2
- package/dist/services/verdict/envelopes.js +2 -2
- package/dist/services/verdict/verdict-aggregator.d.ts +3 -3
- package/dist/services/workflow/autonomous-resume-writer.js +1 -1
- package/dist/services/workflow/plan-reader.d.ts +1 -1
- package/dist/services/workflow/plan-reader.js +1 -1
- package/dist/services/workflow/plan-refresher.d.ts +1 -1
- package/dist/services/workflow/plan-refresher.js +1 -1
- package/dist/services/workflow/plan-trigger-detector.d.ts +1 -1
- package/dist/services/workflow/plan-trigger-detector.js +1 -1
- package/dist/services/workflow/workflow-router-service.js +11 -9
- package/dist/services/workspace/migrate-service.js +1 -1
- package/dist/services/workspace/workspace-service.js +1 -1
- package/package.json +14 -8
- package/scripts/copy-templates.mjs +16 -0
- package/scripts/sync-version.mjs +8 -1
- package/skills/bee/peaks-rd/SKILL.md +14 -1
- package/skills/peaks-code/SKILL.md +44 -0
- package/skills/peaks-code/references/sub-agent-dispatch.md +30 -0
- package/dist/services/agent/ecc-cache-service.d.ts +0 -97
- package/dist/services/agent/ecc-cache-service.js +0 -491
- package/dist/services/audit-independent/perf-audit-service.d.ts +0 -151
- package/dist/services/audit-independent/perf-audit-service.js +0 -316
- package/dist/services/audit-independent/security-audit-service.d.ts +0 -155
- package/dist/services/audit-independent/security-audit-service.js +0 -316
- package/dist/services/context/dispatch-context-guard.d.ts +0 -27
- package/dist/services/context/dispatch-context-guard.js +0 -192
- package/dist/services/context/shared-channel.d.ts +0 -93
- package/dist/services/context/shared-channel.js +0 -308
- package/dist/services/crystallization/crystallization-service.d.ts +0 -239
- package/dist/services/crystallization/crystallization-service.js +0 -397
- package/dist/services/crystallization/crystallization-store.d.ts +0 -75
- package/dist/services/crystallization/crystallization-store.js +0 -193
- package/dist/services/crystallization/crystallization-types.d.ts +0 -313
- package/dist/services/crystallization/crystallization-types.js +0 -299
- package/dist/services/crystallization/evidence-brief-builder.d.ts +0 -237
- package/dist/services/crystallization/evidence-brief-builder.js +0 -240
- package/dist/services/doctor/doctor-service.d.ts +0 -149
- package/dist/services/doctor/doctor-service.js +0 -929
- package/dist/services/filesystem/file-lock.d.ts +0 -11
- package/dist/services/filesystem/file-lock.js +0 -152
- package/dist/services/final-review/final-review-service.d.ts +0 -12
- package/dist/services/final-review/final-review-service.js +0 -52
- package/dist/services/final-review/final-review-types.d.ts +0 -42
- package/dist/services/final-review/final-review-types.js +0 -18
- package/dist/services/job/job-resource-snapshot.d.ts +0 -7
- package/dist/services/job/job-resource-snapshot.js +0 -75
- package/dist/services/mut/assert-scanner.d.ts +0 -6
- package/dist/services/mut/assert-scanner.js +0 -116
- package/dist/services/mut/index.d.ts +0 -8
- package/dist/services/mut/index.js +0 -7
- package/dist/services/mut/mut-runner.d.ts +0 -39
- package/dist/services/mut/mut-runner.js +0 -29
- package/dist/services/mut/production-stryker.d.ts +0 -2
- package/dist/services/mut/production-stryker.js +0 -75
- package/dist/services/mut/report-builder.d.ts +0 -11
- package/dist/services/mut/report-builder.js +0 -110
- package/dist/services/mut/report-loader.d.ts +0 -31
- package/dist/services/mut/report-loader.js +0 -78
- package/dist/services/mut/thresholds.d.ts +0 -21
- package/dist/services/mut/thresholds.js +0 -22
- package/dist/services/mut/types.d.ts +0 -336
- package/dist/services/mut/types.js +0 -59
- package/dist/shared/fs.d.ts +0 -4
- package/dist/shared/fs.js +0 -26
- package/dist/shared/paths.d.ts +0 -6
- package/dist/shared/paths.js +0 -50
- package/dist/shared/result.d.ts +0 -22
- package/dist/shared/result.js +0 -46
- package/dist/shared/version.d.ts +0 -1
- package/dist/shared/version.js +0 -1
- package/examples/video-demo/README.md +0 -142
- package/examples/video-demo/out/en-closing-960.png +0 -0
- package/examples/video-demo/out/zh-closing-960.png +0 -0
- package/examples/video-demo/preview/peaks-loop-demo-en.mp4 +0 -0
- 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 {};
|