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

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,491 +0,0 @@
1
- /**
2
- * ECC cache service — Slice 3 of 4.0.0-beta.11.
3
- *
4
- * Drop-in replacement for the deleted `ecc-agent-service.ts`. The
5
- * pre-Slice-3 implementation shelled out to `npx ecc agent run ...`,
6
- * but the upstream `affaan-m/everything-claude-code` v2.0.0 release
7
- * has NO `ecc` binary — the repo is `agents/*.md` flat files plus
8
- * SKILL.md descriptors. The peaks-loop path is therefore download
9
- * + cache only; the LLM consumes cached `agents/*.md` directly
10
- * through `peaks ecc show <name>`.
11
- *
12
- * Six exports per RD §2:
13
- * - setCacheDirPermissions
14
- * - downloadToCache
15
- * - readCacheManifest
16
- * - listCachedAgents
17
- * - readAgentSkill
18
- * - cleanupStaleCache
19
- *
20
- * The cache layout is `~/.peaks/cache/ecc-installed.json` (manifest)
21
- * + `~/.peaks/cache/ecc-<sha>/agents/<name>.md`. The manifest is
22
- * the active-cache pointer; cleanupStaleCache iterates the on-disk
23
- * sha directories and decides based on the manifest's `fetchedAt`
24
- * (active) vs `mtimeMs` (orphan).
25
- *
26
- * Hard contracts (Karpathy #2 Simplicity First):
27
- * - No subprocess plumbing. Network is `fetch()` only.
28
- * - No factory; one module, six functions, six dependencies.
29
- * - Strict selective extract: agents/*.md only, no symlinks, no
30
- * `..`, no absolute paths, no devices.
31
- * - D-009 fallback: parseFrontmatter throws on malformed input —
32
- * wrap it in try/catch and synthesize `{name, description}`
33
- * from the file name + first non-empty body line.
34
- * - D-010 fallback: PRD's `ecc.tar.gz` asset URL does NOT exist
35
- * upstream; try PRD first, fall back to GitHub's `tarball_url`
36
- * on the release JSON.
37
- */
38
- import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from 'node:fs';
39
- import { join } from 'node:path';
40
- import { homedir } from 'node:os';
41
- import { parseFrontmatter } from '../../shared/frontmatter.js';
42
- export const ECC_REPO_OWNER = 'affaan-m';
43
- export const ECC_REPO_NAME = 'everything-claude-code';
44
- const ECC_CACHE_VERSION = '1';
45
- const ECC_TARBALL_BASENAME = 'ecc.tar.gz';
46
- /**
47
- * Cache dir resolution — single source of truth. Tests inject
48
- * via `dirOverride` on the functions that take one.
49
- */
50
- export function resolveEccCacheDir() {
51
- return join(homedir(), '.peaks', 'cache');
52
- }
53
- export function resolveManifestPath(dirOverride) {
54
- return join(dirOverride ?? resolveEccCacheDir(), 'ecc-installed.json');
55
- }
56
- export function resolveShaDir(sha, dirOverride) {
57
- return join(dirOverride ?? resolveEccCacheDir(), `ecc-${sha}`);
58
- }
59
- export function resolveAgentsDir(sha, dirOverride) {
60
- return join(resolveShaDir(sha, dirOverride), 'agents');
61
- }
62
- /**
63
- * Best-effort chmod 0o700 on POSIX; no-op on Windows (NTFS uses
64
- * ACLs, not POSIX mode bits). Swallows errors with WARN so a
65
- * permissions failure never blocks the CLI.
66
- */
67
- export function setCacheDirPermissions(cacheDir) {
68
- if (process.platform === 'win32')
69
- return;
70
- try {
71
- chmodSync(cacheDir, 0o700);
72
- }
73
- catch {
74
- /* best-effort */
75
- }
76
- }
77
- function isSafeAgentName(name) {
78
- return /^[a-z][a-z0-9-]*$/.test(name);
79
- }
80
- function isSafeArchiveEntry(entryName) {
81
- if (entryName.length === 0)
82
- return false;
83
- if (entryName.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(entryName))
84
- return false;
85
- if (entryName.includes('..'))
86
- return false;
87
- // Strip the leading "<repo-root>/" segment (github tarballs prefix
88
- // every entry with "<repo>-<sha>/..."); everything after must
89
- // start with "agents/" and end with ".md" at a single level.
90
- const segments = entryName.split('/');
91
- // Drop the first "<repo>-<sha>" segment if present.
92
- if (segments.length >= 3 && segments[0]?.startsWith(`${ECC_REPO_NAME}-`)) {
93
- segments.shift();
94
- }
95
- if (segments.length !== 2)
96
- return false;
97
- return segments[0] === 'agents' && segments[1]?.endsWith('.md') === true;
98
- }
99
- function safeAgentNameFromEntry(entryName) {
100
- const segments = entryName.split('/');
101
- if (segments.length >= 3 && segments[0]?.startsWith(`${ECC_REPO_NAME}-`)) {
102
- segments.shift();
103
- }
104
- const file = segments[segments.length - 1] ?? '';
105
- const base = file.replace(/\.md$/i, '');
106
- return isSafeAgentName(base) ? base : null;
107
- }
108
- /**
109
- * Best-effort fetch of the upstream release JSON. Returns the
110
- * parsed body, or null on any network/parse error. Used as the
111
- * D-010 fallback path.
112
- */
113
- async function fetchReleaseJson(apiBase) {
114
- try {
115
- const res = await fetch(apiBase, {
116
- headers: { accept: 'application/vnd.github+json', 'user-agent': 'peaks-loop' },
117
- });
118
- if (!res.ok)
119
- return null;
120
- return (await res.json());
121
- }
122
- catch {
123
- return null;
124
- }
125
- }
126
- /**
127
- * Resolve a 40-char commit SHA for `tag`. Prefers the GitHub
128
- * releases API (which dereferences annotated tags to commit SHA
129
- * via `target_commitish`). Falls back to the tag name if the
130
- * upstream returns a non-SHA-shaped identifier (older tags).
131
- */
132
- async function resolveCommitSha(ref) {
133
- const apiBase = `https://api.github.com/repos/${ECC_REPO_OWNER}/${ECC_REPO_NAME}/releases/tags/${encodeURIComponent(ref)}`;
134
- const release = await fetchReleaseJson(apiBase);
135
- const tagName = release?.tag_name ?? ref;
136
- if (/^[0-9a-f]{40}$/.test(tagName))
137
- return tagName;
138
- return tagName;
139
- }
140
- function asSha(value) {
141
- return /^[0-9a-f]{40}$/.test(value) ? value : value;
142
- }
143
- /**
144
- * Tiny tar.gz extractor. Public-domain-compatible tar parser:
145
- * - reads 512-byte header blocks
146
- * - validates name + size
147
- * - rejects anything that is not a regular file at a safe path
148
- * - filters to the `agents/*.md` allowlist via isSafeArchiveEntry
149
- *
150
- * We do NOT decompress gzip via a third-party lib; Node's
151
- * `node:zlib` ships with `gunzip` so the implementation is
152
- * self-contained.
153
- */
154
- async function extractAgentsFromTarGz(buffer, outDir) {
155
- if (!existsSync(outDir))
156
- mkdirSync(outDir, { recursive: true });
157
- const extracted = [];
158
- // Lazy import to keep the cold path cheap.
159
- const { gunzipSync } = await import('node:zlib');
160
- let tar;
161
- try {
162
- tar = gunzipSync(buffer);
163
- }
164
- catch {
165
- return [];
166
- }
167
- const BLOCK = 512;
168
- let offset = 0;
169
- while (offset + BLOCK <= tar.length) {
170
- const header = tar.subarray(offset, offset + BLOCK);
171
- // Two consecutive zero blocks mark end-of-archive.
172
- if (header.every((b) => b === 0))
173
- break;
174
- const name = readCString(header, 0, 100);
175
- if (name === null || name.length === 0)
176
- break;
177
- const sizeOctal = readCString(header, 124, 12);
178
- if (sizeOctal === null)
179
- break;
180
- const size = parseOctal(sizeOctal);
181
- if (size === null)
182
- break;
183
- const typeFlag = String.fromCharCode(header[156] ?? 0);
184
- // '0' or '\0' = regular file. Reject symlinks (2), dirs (5), etc.
185
- if (typeFlag !== '0' && typeFlag !== '\0') {
186
- offset += BLOCK + Math.ceil(size / BLOCK) * BLOCK;
187
- continue;
188
- }
189
- if (!isSafeArchiveEntry(name)) {
190
- offset += BLOCK + Math.ceil(size / BLOCK) * BLOCK;
191
- continue;
192
- }
193
- const safeName = safeAgentNameFromEntry(name);
194
- if (safeName === null) {
195
- offset += BLOCK + Math.ceil(size / BLOCK) * BLOCK;
196
- continue;
197
- }
198
- const data = tar.subarray(offset + BLOCK, offset + BLOCK + size);
199
- writeFileSync(join(outDir, `${safeName}.md`), data);
200
- extracted.push(safeName);
201
- offset += BLOCK + Math.ceil(size / BLOCK) * BLOCK;
202
- }
203
- return extracted;
204
- }
205
- function readCString(buf, start, len) {
206
- let end = start;
207
- while (end < start + len && buf[end] !== 0)
208
- end += 1;
209
- if (end === start)
210
- return '';
211
- try {
212
- return Buffer.from(buf.subarray(start, end)).toString('utf8');
213
- }
214
- catch {
215
- return null;
216
- }
217
- }
218
- function parseOctal(value) {
219
- const trimmed = value.trim().replace(/\0+$/g, '');
220
- if (trimmed.length === 0)
221
- return 0;
222
- const n = Number.parseInt(trimmed, 8);
223
- return Number.isFinite(n) && n >= 0 ? n : null;
224
- }
225
- async function fetchBuffer(url) {
226
- try {
227
- const res = await fetch(url, {
228
- headers: { accept: 'application/octet-stream', 'user-agent': 'peaks-loop' },
229
- });
230
- if (!res.ok)
231
- return null;
232
- const ab = await res.arrayBuffer();
233
- return new Uint8Array(ab);
234
- }
235
- catch {
236
- return null;
237
- }
238
- }
239
- async function downloadTarball(ref, sha, outDir) {
240
- // D-010: PRD URL first, GitHub fallback second.
241
- const prdUrl = `https://github.com/${ECC_REPO_OWNER}/${ECC_REPO_NAME}/releases/download/${encodeURIComponent(ref)}/${ECC_TARBALL_BASENAME}`;
242
- let buffer = await fetchBuffer(prdUrl);
243
- if (buffer === null) {
244
- const release = await fetchReleaseJson(`https://api.github.com/repos/${ECC_REPO_OWNER}/${ECC_REPO_NAME}/releases/tags/${encodeURIComponent(ref)}`);
245
- const tarballUrl = release?.tarball_url;
246
- if (typeof tarballUrl === 'string' && tarballUrl.length > 0) {
247
- buffer = await fetchBuffer(tarballUrl);
248
- }
249
- if (buffer === null && Array.isArray(release?.assets)) {
250
- const asset = release.assets.find((a) => a.name === ECC_TARBALL_BASENAME) ??
251
- release.assets.find((a) => a.name === `${ECC_REPO_NAME}-universal-${ref}.tgz`) ??
252
- release.assets.find((a) => a.name.endsWith('.tgz') || a.name.endsWith('.tar.gz'));
253
- if (asset)
254
- buffer = await fetchBuffer(asset.browser_download_url);
255
- }
256
- }
257
- if (buffer === null) {
258
- throw new Error('fetch-failed');
259
- }
260
- return extractAgentsFromTarGz(buffer, outDir);
261
- }
262
- /**
263
- * Download ECC into `~/.peaks/cache/ecc-<sha>/agents/`. Returns
264
- * the resolved SHA + the number of agents extracted.
265
- *
266
- * Throws `Error('fetch-failed')` on network failure so the CLI
267
- * layer can render the manual-install instructions.
268
- */
269
- export async function downloadToCache({ ref } = {}) {
270
- const requestedRef = ref ?? 'latest';
271
- let resolvedSha;
272
- if (requestedRef === 'latest') {
273
- const release = await fetchReleaseJson(`https://api.github.com/repos/${ECC_REPO_OWNER}/${ECC_REPO_NAME}/releases/latest`);
274
- const tag = release?.tag_name;
275
- if (typeof tag !== 'string' || tag.length === 0) {
276
- throw new Error('fetch-failed');
277
- }
278
- resolvedSha = asSha(await resolveCommitSha(tag) ?? tag);
279
- }
280
- else {
281
- resolvedSha = asSha(await resolveCommitSha(requestedRef) ?? requestedRef);
282
- }
283
- const cacheDir = resolveEccCacheDir();
284
- if (!existsSync(cacheDir))
285
- mkdirSync(cacheDir, { recursive: true });
286
- setCacheDirPermissions(cacheDir);
287
- const agentsDir = resolveAgentsDir(resolvedSha, cacheDir);
288
- if (existsSync(agentsDir) && readdirSync(agentsDir).length > 0) {
289
- // Already populated; emit manifest + return.
290
- const agents = readdirSync(agentsDir)
291
- .filter((f) => f.endsWith('.md'))
292
- .map((f) => f.replace(/\.md$/i, ''));
293
- writeManifest({ version: ECC_CACHE_VERSION, sha: resolvedSha, fetchedAt: new Date().toISOString(), agents });
294
- return { sha: resolvedSha, agents: agents.length };
295
- }
296
- const extracted = await downloadTarball(requestedRef === 'latest' ? resolvedSha : requestedRef, resolvedSha, agentsDir);
297
- const manifest = {
298
- version: ECC_CACHE_VERSION,
299
- sha: resolvedSha,
300
- fetchedAt: new Date().toISOString(),
301
- agents: extracted,
302
- };
303
- writeManifest(manifest);
304
- return { sha: resolvedSha, agents: extracted.length };
305
- }
306
- function writeManifest(manifest) {
307
- const path = resolveManifestPath();
308
- writeFileSync(path, JSON.stringify(manifest, null, 2));
309
- }
310
- export function readCacheManifest() {
311
- const path = resolveManifestPath();
312
- if (!existsSync(path))
313
- return null;
314
- try {
315
- const raw = readFileSync(path, 'utf8');
316
- const parsed = JSON.parse(raw);
317
- if (typeof parsed.version === 'string' &&
318
- typeof parsed.sha === 'string' &&
319
- typeof parsed.fetchedAt === 'string' &&
320
- Array.isArray(parsed.agents)) {
321
- return parsed;
322
- }
323
- return null;
324
- }
325
- catch {
326
- return null;
327
- }
328
- }
329
- let warnedAboutFallback = false;
330
- function fallbackMetadata(fileName) {
331
- const base = fileName.replace(/\.md$/i, '');
332
- let description = '';
333
- try {
334
- const body = readFileSync(join(resolveManifestDirForAgents(), fileName), 'utf8');
335
- const lines = body.split(/\r?\n/);
336
- let inFrontmatter = false;
337
- let seenClosing = false;
338
- for (const raw of lines) {
339
- const line = raw.trim();
340
- if (!seenClosing) {
341
- if (!inFrontmatter && line === '---') {
342
- inFrontmatter = true;
343
- continue;
344
- }
345
- if (inFrontmatter && line === '---') {
346
- seenClosing = true;
347
- continue;
348
- }
349
- continue;
350
- }
351
- if (line.length === 0)
352
- continue;
353
- description = line;
354
- break;
355
- }
356
- }
357
- catch {
358
- /* best-effort */
359
- }
360
- if (description.length > 80)
361
- description = `${description.slice(0, 77)}...`;
362
- if (!warnedAboutFallback) {
363
- try {
364
- process.stderr.write(`warning: cached agent "${base}" has malformed frontmatter; falling back to filename + first body line\n`);
365
- }
366
- catch {
367
- /* best-effort */
368
- }
369
- warnedAboutFallback = true;
370
- }
371
- return { name: base, description };
372
- }
373
- function resolveManifestDirForAgents() {
374
- const manifest = readCacheManifest();
375
- if (manifest === null)
376
- return '';
377
- return resolveAgentsDir(manifest.sha);
378
- }
379
- export function listCachedAgents() {
380
- const manifest = readCacheManifest();
381
- if (manifest === null)
382
- return [];
383
- const agentsDir = resolveAgentsDir(manifest.sha);
384
- if (!existsSync(agentsDir))
385
- return [];
386
- const out = [];
387
- for (const file of readdirSync(agentsDir).sort()) {
388
- if (!file.endsWith('.md'))
389
- continue;
390
- const fullPath = join(agentsDir, file);
391
- let meta;
392
- try {
393
- const raw = readFileSync(fullPath, 'utf8');
394
- const fm = parseFrontmatter(raw);
395
- meta = { name: fm.name, description: fm.description };
396
- }
397
- catch {
398
- // D-009 fallback — name from filename, description from first body line.
399
- meta = fallbackMetadata(file);
400
- }
401
- out.push(meta);
402
- }
403
- return out;
404
- }
405
- export function readAgentSkill(name) {
406
- if (!isSafeAgentName(name))
407
- return null;
408
- const manifest = readCacheManifest();
409
- if (manifest === null)
410
- return null;
411
- const file = join(resolveAgentsDir(manifest.sha), `${name}.md`);
412
- if (!existsSync(file))
413
- return null;
414
- try {
415
- return readFileSync(file, 'utf8');
416
- }
417
- catch {
418
- return null;
419
- }
420
- }
421
- /**
422
- * 7-day TTL sweep over `ecc-<sha>/` directories.
423
- *
424
- * - Active cache (per manifest `sha`): use `fetchedAt` as the
425
- * age source; remove if older than `retentionDays`.
426
- * - Orphan caches (any other `ecc-<40hex>` directory): use
427
- * directory `mtimeMs` as the age source.
428
- * - If the active cache is removed, invalidate the manifest.
429
- *
430
- * Returns the absolute paths of the removed directories.
431
- */
432
- export function cleanupStaleCache({ retentionDays, nowMs, dirOverride, }) {
433
- const cacheDir = dirOverride ?? resolveEccCacheDir();
434
- if (!existsSync(cacheDir))
435
- return { removed: [] };
436
- const dayMs = 24 * 60 * 60 * 1000;
437
- const cutoff = nowMs - retentionDays * dayMs;
438
- const manifest = readCacheManifest();
439
- const activeSha = manifest?.sha ?? null;
440
- const activeFetchedAt = manifest?.fetchedAt ? Date.parse(manifest.fetchedAt) : NaN;
441
- const removed = [];
442
- let names;
443
- try {
444
- names = readdirSync(cacheDir);
445
- }
446
- catch {
447
- return { removed: [] };
448
- }
449
- for (const name of names) {
450
- const match = /^ecc-([0-9a-f]{40})$/.exec(name);
451
- if (match === null)
452
- continue;
453
- const sha = match[1] ?? '';
454
- const fullPath = join(cacheDir, name);
455
- let stat;
456
- try {
457
- stat = statSync(fullPath);
458
- }
459
- catch {
460
- continue;
461
- }
462
- if (!stat.isDirectory())
463
- continue;
464
- let ageMs;
465
- if (sha === activeSha && Number.isFinite(activeFetchedAt)) {
466
- ageMs = nowMs - activeFetchedAt;
467
- }
468
- else {
469
- ageMs = nowMs - stat.mtimeMs;
470
- }
471
- if (ageMs > retentionDays * dayMs) {
472
- try {
473
- rmSync(fullPath, { recursive: true, force: true });
474
- removed.push(fullPath);
475
- }
476
- catch {
477
- /* best-effort */
478
- }
479
- }
480
- }
481
- // Invalidate manifest if active cache was removed.
482
- if (activeSha !== null && removed.some((p) => p.includes(`ecc-${activeSha}`))) {
483
- try {
484
- rmSync(resolveManifestPath(cacheDir), { force: true });
485
- }
486
- catch {
487
- /* best-effort */
488
- }
489
- }
490
- return { removed };
491
- }
@@ -1,151 +0,0 @@
1
- /**
2
- * peaks-perf-audit service — independent performance audit skill driver.
3
- *
4
- * Slice v2.12.0 (Group A, Tier 3). Symmetric to security-audit-service.ts:
5
- * - 5-state detection of the perf-audit runtime
6
- * - Loading + sha256 verification of the prd/handoff.md
7
- * - Loading the project-level perf-template.md
8
- * - Producing the audit envelope (verdict + violations) to write
9
- * to `.peaks/_runtime/<sid>/audit/perf-<rid>.md`
10
- *
11
- * The actual LLM-in-loop audit step is NOT in this service — the
12
- * parent LLM (peaks-perf-audit skill prompt) reads the template,
13
- * runs the audit, and returns a structured envelope. This service
14
- * is the pure I/O + validation core; the LLM is the judgement core.
15
- *
16
- * Cross-references:
17
- * - PRD: `.peaks/_runtime/<sid>/prd/handoff.md` (sha256-locked, schemaVersion: 2)
18
- * - Template: `.peaks/project-scan/perf-template.md`
19
- * - Output: `.peaks/_runtime/<sid>/audit/perf-<rid>.md`
20
- * - Schema: `.peaks/project-scan/audit-output-schema.md` (schemaVersion: 1)
21
- * - Companion: `src/services/audit-independent/security-audit-service.ts`
22
- */
23
- /**
24
- * 5-state detection result. Mirrors `SecurityAuditDetectState` and
25
- * the ecc-bridge `detectEcc` shape (slice 7 Group D).
26
- *
27
- * - `ready` — handoff + template + project all present
28
- * - `handoff-missing` — `.peaks/_runtime/<sid>/prd/handoff.md` absent
29
- * - `template-missing` — `.peaks/project-scan/perf-template.md` absent
30
- * - `dispatch-failed` — parent LLM threw before returning the audit envelope
31
- * - `envelope-malformed` — parent LLM returned a value that fails `isPerfAuditEnvelope`
32
- */
33
- export type PerfAuditDetectState = 'ready' | 'handoff-missing' | 'template-missing' | 'dispatch-failed' | 'envelope-malformed';
34
- export interface PerfAuditDetectResult {
35
- readonly state: PerfAuditDetectState;
36
- readonly handoffPresent: boolean;
37
- readonly templatePresent: boolean;
38
- readonly warnings: readonly string[];
39
- readonly nextActions: readonly string[];
40
- }
41
- export type PerfAuditVerdict = 'pass' | 'warn' | 'block';
42
- export interface PerfAuditViolation {
43
- readonly dimension: string;
44
- readonly severity: 'CRITICAL' | 'HIGH' | 'MED' | 'LOW';
45
- readonly file: string;
46
- readonly line: number;
47
- readonly hint: string;
48
- }
49
- export interface PerfAuditEnvelope {
50
- readonly verdict: PerfAuditVerdict;
51
- readonly violations: ReadonlyArray<PerfAuditViolation>;
52
- readonly summary: string;
53
- }
54
- /**
55
- * Validate a raw value as a PerfAuditEnvelope. Mirrors the
56
- * `isSecurityAuditEnvelope` strict-shape pattern.
57
- */
58
- export declare function isPerfAuditEnvelope(value: unknown): value is PerfAuditEnvelope;
59
- /**
60
- * Handoff frontmatter — minimal projection of peaks-prd v2.11.0 handoff
61
- * frontmatter. We only need sha256 for verification; the rest of the
62
- * handoff body is consumed by the parent LLM, not by this service.
63
- *
64
- * Symmetric to `HandoffFrontmatter` in `security-audit-service.ts`.
65
- * We duplicate the type (rather than share) so the two services
66
- * can be independently type-evolved.
67
- */
68
- export interface HandoffFrontmatter {
69
- readonly sha256: string;
70
- readonly schemaVersion: number;
71
- }
72
- /**
73
- * Read the peaks-prd handoff frontmatter + body, verify sha256 matches
74
- * the body. Returns null on missing file or sha256 mismatch; otherwise
75
- * returns the parsed frontmatter + body string.
76
- *
77
- * Defensive: we read frontmatter via a 1-line regex match, not via
78
- * a YAML parser. The handoff service (`src/services/prd/handoff-service.ts`)
79
- * is the canonical writer; we only consume its output here.
80
- */
81
- export declare function readAndVerifyHandoff(handoffPath: string, projectRoot: string): {
82
- frontmatter: HandoffFrontmatter;
83
- body: string;
84
- } | null;
85
- /**
86
- * Read the project-level perf-template.md. Returns the body
87
- * string (with frontmatter stripped) or null if the file is absent.
88
- * Does NOT validate the template content; the parent LLM validates
89
- * by reading.
90
- */
91
- export declare function readPerfTemplate(projectRoot: string): string | null;
92
- /**
93
- * 5-state detector. The parent skill inspects the state and either
94
- * proceeds (when `ready`) or records a degradation note and falls
95
- * back (when any other state).
96
- *
97
- * Soft-fail policy mirrors `detectEcc` (slice 7 Group D) and
98
- * `detectSecurityAudit`: the CLI never throws; it returns a typed
99
- * detect result.
100
- */
101
- export declare function detectPerfAudit(input: {
102
- readonly projectRoot: string;
103
- readonly sessionId: string;
104
- readonly dispatchError?: unknown;
105
- readonly envelope?: unknown;
106
- }): PerfAuditDetectResult;
107
- /**
108
- * Render a PerfAuditEnvelope to the canonical audit artifact body
109
- * (markdown). Mirrors `renderSecurityAuditArtifact` in
110
- * `security-audit-service.ts` and `adaptEccEnvelopeToRdCodeReview`
111
- * in ecc-bridge.ts: produce a markdown body with all required
112
- * sections, ready to write to
113
- * `.peaks/_runtime/<sid>/audit/perf-<rid>.md`.
114
- */
115
- export declare function renderPerfAuditArtifact(env: PerfAuditEnvelope, opts: {
116
- rid: string;
117
- handoffHash: string;
118
- generatedAt: string;
119
- }): {
120
- body: string;
121
- violationsCount: number;
122
- verdict: PerfAuditVerdict;
123
- };
124
- /**
125
- * Write the rendered audit artifact to
126
- * `.peaks/_runtime/<sid>/audit/perf-<rid>.md`.
127
- *
128
- * Atomic write (tmp + rename) to avoid partial writes on crash.
129
- * Returns the absolute path on success.
130
- */
131
- export declare function writePerfAuditArtifact(projectRoot: string, sessionId: string, rid: string, body: string): string;
132
- /**
133
- * Convenience: run `detectPerfAudit` + `renderPerfAuditArtifact`
134
- * + `writePerfAuditArtifact` in one call.
135
- *
136
- * Returns `{ detect, artifactPath, violationsCount, verdict }` or
137
- * `{ detect, artifactPath: null, ... }` when not ready.
138
- */
139
- export declare function runPerfAudit(input: {
140
- readonly projectRoot: string;
141
- readonly sessionId: string;
142
- readonly rid: string;
143
- readonly generatedAt: string;
144
- readonly dispatchError?: unknown;
145
- readonly envelope?: unknown;
146
- }): {
147
- detect: PerfAuditDetectResult;
148
- artifactPath: string | null;
149
- violationsCount: number;
150
- verdict: PerfAuditVerdict | null;
151
- };