peaks-loop 4.0.29 → 4.0.31

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 (75) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cli/commands/_register.js +4 -0
  3. package/dist/cli/commands/dispatch-commands.d.ts +5 -41
  4. package/dist/cli/commands/dispatch-commands.js +35 -185
  5. package/dist/cli/commands/evidence-commands.d.ts +12 -0
  6. package/dist/cli/commands/evidence-commands.js +43 -0
  7. package/dist/cli/commands/fresh-context-commands.d.ts +15 -0
  8. package/dist/cli/commands/fresh-context-commands.js +24 -0
  9. package/dist/cli/commands/request-commands.js +2 -96
  10. package/dist/cli/commands/request-format-helpers.d.ts +19 -0
  11. package/dist/cli/commands/request-format-helpers.js +102 -0
  12. package/dist/cli/commands/workspace/init-command.js +27 -14
  13. package/dist/cli/commands/worktree-auth-commands.js +2 -472
  14. package/dist/cli/commands/worktree-lease-commands.d.ts +21 -0
  15. package/dist/cli/commands/worktree-lease-commands.js +500 -0
  16. package/dist/services/artifacts/request-artifact-service.js +13 -3
  17. package/dist/services/code/auto-compact-lifecycle.d.ts +102 -0
  18. package/dist/services/code/auto-compact-lifecycle.js +235 -0
  19. package/dist/services/code/auto-compact-orchestrator.d.ts +1 -1
  20. package/dist/services/code/auto-compact-orchestrator.js +1 -222
  21. package/dist/services/codegraph/codegraph-autorefresh.js +44 -6
  22. package/dist/services/codegraph/codegraph-preflight-service.d.ts +5 -2
  23. package/dist/services/codegraph/codegraph-preflight-service.js +13 -6
  24. package/dist/services/codegraph/codegraph-service.d.ts +17 -2
  25. package/dist/services/codegraph/codegraph-service.js +27 -2
  26. package/dist/services/context/build-dispatch-system-prompt.d.ts +26 -0
  27. package/dist/services/context/build-dispatch-system-prompt.js +38 -3
  28. package/dist/services/dispatch/dispatch-record-types.d.ts +278 -0
  29. package/dist/services/dispatch/dispatch-record-types.js +15 -0
  30. package/dist/services/dispatch/dispatch-record-upgrade.d.ts +5 -0
  31. package/dist/services/dispatch/dispatch-record-upgrade.js +230 -0
  32. package/dist/services/dispatch/dispatch-record-writer.d.ts +3 -278
  33. package/dist/services/dispatch/dispatch-record-writer.js +3 -245
  34. package/dist/services/dispatch/dispatch-sub-agent.d.ts +43 -0
  35. package/dist/services/dispatch/dispatch-sub-agent.js +55 -0
  36. package/dist/services/dispatch/isolation-lease.d.ts +43 -0
  37. package/dist/services/dispatch/isolation-lease.js +129 -0
  38. package/dist/services/evidence/evidence-generator.d.ts +26 -0
  39. package/dist/services/evidence/evidence-generator.js +349 -0
  40. package/dist/services/fresh-context/config.d.ts +1 -0
  41. package/dist/services/fresh-context/config.js +13 -0
  42. package/dist/services/fresh-context/fresh-context-block.d.ts +12 -0
  43. package/dist/services/fresh-context/fresh-context-block.js +40 -0
  44. package/dist/services/fresh-context/trigger-scan.d.ts +30 -0
  45. package/dist/services/fresh-context/trigger-scan.js +29 -0
  46. package/dist/services/skills/hooks-codegate-superpowers.d.ts +65 -0
  47. package/dist/services/skills/hooks-codegate-superpowers.js +204 -0
  48. package/dist/services/skills/hooks-settings-service.d.ts +2 -64
  49. package/dist/services/skills/hooks-settings-service.js +2 -215
  50. package/dist/services/skills/skill-statusline-renderer.d.ts +2 -34
  51. package/dist/services/skills/skill-statusline-renderer.js +1 -184
  52. package/dist/services/skills/statusline-palette.d.ts +62 -0
  53. package/dist/services/skills/statusline-palette.js +190 -0
  54. package/dist/services/slice/slice-decompose-import-edges.d.ts +8 -0
  55. package/dist/services/slice/slice-decompose-import-edges.js +102 -0
  56. package/dist/services/slice/slice-decompose-service.js +3 -198
  57. package/dist/services/slice/slice-decompose-tarjan.d.ts +8 -0
  58. package/dist/services/slice/slice-decompose-tarjan.js +108 -0
  59. package/dist/services/standards/project-standards-service.d.ts +1 -9
  60. package/dist/services/standards/project-standards-service.js +3 -232
  61. package/dist/services/standards/standards-render.d.ts +23 -0
  62. package/dist/services/standards/standards-render.js +238 -0
  63. package/dist/services/standards/ui-library-dispatch-block.d.ts +27 -0
  64. package/dist/services/standards/ui-library-dispatch-block.js +48 -0
  65. package/dist/services/workspace/reconcile-migrate.d.ts +77 -0
  66. package/dist/services/workspace/reconcile-migrate.js +230 -0
  67. package/dist/services/workspace/reconcile-service.d.ts +0 -72
  68. package/dist/services/workspace/reconcile-service.js +3 -220
  69. package/dist/services/workspace/workspace-service.js +13 -2
  70. package/dist/shared/incrementing-number.d.ts +11 -0
  71. package/dist/shared/incrementing-number.js +16 -3
  72. package/package.json +5 -5
  73. package/skills/peaks-code/SKILL.md +6 -2
  74. package/skills/peaks-code/references/fresh-context-preflight.md +81 -0
  75. package/skills/peaks-code/references/sub-agent-dispatch.md +27 -8
@@ -18,8 +18,10 @@
18
18
  * Tests inject fakes via the `codegraphRunner`, `understandRunner`, and
19
19
  * `importEdgeRunner` fields of `DecomposeOptions`.
20
20
  */
21
- import { join, relative, dirname, basename } from 'node:path';
21
+ import { basename } from 'node:path';
22
22
  import { calibrate } from './calibration-store.js';
23
+ import { buildDependencyEdges } from './slice-decompose-import-edges.js';
24
+ import { findSCCs } from './slice-decompose-tarjan.js';
23
25
  export async function decomposeSlices(rid, prdMarkdown, projectRoot, options = {}) {
24
26
  const runners = resolveRunners(options);
25
27
  const stageOrder = [
@@ -217,101 +219,6 @@ function applyImplicitWuFallback(workUnits, allFiles) {
217
219
  candidates: [`file:${file}`]
218
220
  }));
219
221
  }
220
- function indexFilesToWorkUnits(wus) {
221
- const fileToWu = new Map();
222
- for (const wu of wus) {
223
- for (const f of wu.files) {
224
- fileToWu.set(f, wu.id);
225
- }
226
- }
227
- return fileToWu;
228
- }
229
- function classifyImportEdge(imp, fileToWu, projectRoot) {
230
- let fromWu = fileToWu.get(imp.from);
231
- let toWu = fileToWu.get(imp.to);
232
- if (!fromWu) {
233
- const resolved = resolveRelativeImport(projectRoot, imp.from, imp.evidence);
234
- fromWu = fileToWu.get(resolved);
235
- }
236
- if (!toWu) {
237
- const resolved = resolveRelativeImport(projectRoot, imp.from, imp.evidence);
238
- toWu = fileToWu.get(resolved);
239
- }
240
- if (!fromWu || !toWu)
241
- return null;
242
- return { fromWu, toWu, evidence: imp.evidence };
243
- }
244
- function pushIfNew(collector, edge) {
245
- if (edge.from === edge.to)
246
- return;
247
- const key = `${edge.from}->${edge.to}|${edge.kind}`;
248
- if (collector.seen.has(key))
249
- return;
250
- collector.seen.add(key);
251
- collector.edges.push(edge);
252
- }
253
- function collectImportEdges(importEdges, fileToWu, projectRoot, collector) {
254
- for (const imp of importEdges) {
255
- const classified = classifyImportEdge(imp, fileToWu, projectRoot);
256
- if (classified === null)
257
- continue;
258
- const edge = {
259
- from: classified.fromWu,
260
- to: classified.toWu,
261
- kind: 'imports',
262
- weight: 10.0,
263
- evidence: classified.evidence,
264
- isSemantic: false,
265
- confidence: 'structural'
266
- };
267
- pushIfNew(collector, edge);
268
- }
269
- }
270
- function collectSemanticEdges(kg, fileToWu, collector) {
271
- for (const e of kg.edges) {
272
- if (e.type !== 'contains_flow' && e.type !== 'flow_step')
273
- continue;
274
- const fromNode = kg.nodes.find((n) => n.id === e.source);
275
- const toNode = kg.nodes.find((n) => n.id === e.target);
276
- if (!fromNode?.filePath || !toNode?.filePath)
277
- continue;
278
- const fromWu = fileToWu.get(fromNode.filePath);
279
- const toWu = fileToWu.get(toNode.filePath);
280
- if (!fromWu || !toWu || fromWu === toWu)
281
- continue;
282
- const weight = e.type === 'flow_step' ? 0.05 : 0.1;
283
- const edge = {
284
- from: fromWu,
285
- to: toWu,
286
- kind: e.type,
287
- weight,
288
- evidence: `understand-anything: ${e.type} ${e.source}->${e.target}`,
289
- isSemantic: true,
290
- confidence: 'semantic'
291
- };
292
- pushIfNew(collector, edge);
293
- }
294
- }
295
- function buildDependencyEdges(wus, importEdges, kg, projectRoot) {
296
- const fileToWu = indexFilesToWorkUnits(wus);
297
- const collector = { edges: [], seen: new Set() };
298
- collectImportEdges(importEdges, fileToWu, projectRoot, collector);
299
- if (kg !== null) {
300
- collectSemanticEdges(kg, fileToWu, collector);
301
- }
302
- return collector.edges;
303
- }
304
- function resolveRelativeImport(projectRoot, sourceFile, evidence) {
305
- const match = /from\s+['"]([^'"]+)['"]/.exec(evidence);
306
- if (!match)
307
- return sourceFile;
308
- const importPath = match[1];
309
- if (!importPath.startsWith('.'))
310
- return sourceFile;
311
- const sourceDir = dirname(sourceFile);
312
- const tsPath = importPath.replace(/\.js$/, '.ts').replace(/\.jsx$/, '.tsx');
313
- return relative(projectRoot, join(projectRoot, sourceDir, tsPath));
314
- }
315
222
  // =====================================================================
316
223
  // Stage 3: Tarjan SCC + longest path
317
224
  // =====================================================================
@@ -321,108 +228,6 @@ function sccCriticalPathStage(state) {
321
228
  const criticalPath = findCriticalPath(state.workUnits, state.depEdges);
322
229
  return { ...state, scc, criticalPath };
323
230
  }
324
- function findSCCs(nodeIds, edges) {
325
- const adj = buildAdjacencyList(nodeIds, edges);
326
- const sccs = runTarjan(nodeIds, adj);
327
- return summariseSccs(sccs, edges);
328
- }
329
- /** Build a forward adjacency map: nodeId → list of distinct neighbours. */
330
- function buildAdjacencyList(nodeIds, edges) {
331
- const adj = new Map();
332
- for (const id of nodeIds)
333
- adj.set(id, []);
334
- for (const e of edges) {
335
- const list = adj.get(e.from);
336
- if (list && !list.includes(e.to))
337
- list.push(e.to);
338
- }
339
- return adj;
340
- }
341
- function createTarjanVisitor() {
342
- return {
343
- index: 0,
344
- idx: new Map(),
345
- lowlink: new Map(),
346
- onStack: new Set(),
347
- stack: [],
348
- sccs: []
349
- };
350
- }
351
- function enterNode(visitor, v) {
352
- visitor.idx.set(v, visitor.index);
353
- visitor.lowlink.set(v, visitor.index);
354
- visitor.index++;
355
- visitor.stack.push(v);
356
- visitor.onStack.add(v);
357
- }
358
- function relaxEdge(visitor, v, w, recurse) {
359
- if (!visitor.idx.has(w)) {
360
- recurse(w);
361
- visitor.lowlink.set(v, Math.min(visitor.lowlink.get(v) ?? 0, visitor.lowlink.get(w) ?? 0));
362
- }
363
- else if (visitor.onStack.has(w)) {
364
- visitor.lowlink.set(v, Math.min(visitor.lowlink.get(v) ?? 0, visitor.idx.get(w) ?? 0));
365
- }
366
- }
367
- function finaliseRoot(visitor, v) {
368
- if ((visitor.lowlink.get(v) ?? 0) !== (visitor.idx.get(v) ?? 0))
369
- return;
370
- const component = [];
371
- let w;
372
- do {
373
- w = visitor.stack.pop();
374
- visitor.onStack.delete(w);
375
- component.push(w);
376
- } while (w !== v);
377
- visitor.sccs.push(component);
378
- }
379
- /** Run Tarjan's SCC algorithm; returns the list of components in
380
- * discovery order. Visitor state is hoisted to a named struct
381
- * (`TarjanVisitor`) so the recursive body can be split into
382
- * enterNode / relaxEdge / finaliseRoot without parameter sprawl. */
383
- function runTarjan(nodeIds, adj) {
384
- const visitor = createTarjanVisitor();
385
- const strongconnect = (v) => {
386
- enterNode(visitor, v);
387
- for (const w of adj.get(v) ?? []) {
388
- relaxEdge(visitor, v, w, strongconnect);
389
- }
390
- finaliseRoot(visitor, v);
391
- };
392
- for (const id of nodeIds) {
393
- if (!visitor.idx.has(id))
394
- strongconnect(id);
395
- }
396
- return visitor.sccs;
397
- }
398
- function summariseSccs(sccs, edges) {
399
- const trivial = [];
400
- const nonTrivial = [];
401
- for (const scc of sccs) {
402
- if (scc.length === 1)
403
- trivial.push(scc[0]);
404
- else
405
- nonTrivial.push(...scc);
406
- }
407
- let condensationEdges = 0;
408
- for (const e of edges) {
409
- if (!sameScc(sccs, e.from, e.to))
410
- condensationEdges++;
411
- }
412
- return {
413
- sccCount: sccs.length,
414
- trivialSCCs: trivial,
415
- nonTrivialSCCs: nonTrivial,
416
- condensationEdges
417
- };
418
- }
419
- function sameScc(sccs, a, b) {
420
- for (const scc of sccs) {
421
- if (scc.includes(a) && scc.includes(b))
422
- return true;
423
- }
424
- return false;
425
- }
426
231
  /** Critical-path FSM: three sequential phases.
427
232
  * 1. `phaseTopoOrder` -- Kahn's algorithm with cycle-tolerance fallback.
428
233
  * 2. `phaseLongestDist` -- DP relaxation along the topo order.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tarjan SCC + summarisation helpers for the slice-decompose pipeline.
3
+ * Extracted from `slice-decompose-service.ts` to keep that file under
4
+ * the 800 LOC cap (mechanical verbatim move). `findSCCs` is the only
5
+ * exported entry point; the rest are internal helpers.
6
+ */
7
+ import type { DependencyEdge, SccAnalysis } from './slice-decompose-types.js';
8
+ export declare function findSCCs(nodeIds: readonly string[], edges: readonly DependencyEdge[]): SccAnalysis;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Tarjan SCC + summarisation helpers for the slice-decompose pipeline.
3
+ * Extracted from `slice-decompose-service.ts` to keep that file under
4
+ * the 800 LOC cap (mechanical verbatim move). `findSCCs` is the only
5
+ * exported entry point; the rest are internal helpers.
6
+ */
7
+ export function findSCCs(nodeIds, edges) {
8
+ const adj = buildAdjacencyList(nodeIds, edges);
9
+ const sccs = runTarjan(nodeIds, adj);
10
+ return summariseSccs(sccs, edges);
11
+ }
12
+ /** Build a forward adjacency map: nodeId → list of distinct neighbours. */
13
+ function buildAdjacencyList(nodeIds, edges) {
14
+ const adj = new Map();
15
+ for (const id of nodeIds)
16
+ adj.set(id, []);
17
+ for (const e of edges) {
18
+ const list = adj.get(e.from);
19
+ if (list && !list.includes(e.to))
20
+ list.push(e.to);
21
+ }
22
+ return adj;
23
+ }
24
+ function createTarjanVisitor() {
25
+ return {
26
+ index: 0,
27
+ idx: new Map(),
28
+ lowlink: new Map(),
29
+ onStack: new Set(),
30
+ stack: [],
31
+ sccs: []
32
+ };
33
+ }
34
+ function enterNode(visitor, v) {
35
+ visitor.idx.set(v, visitor.index);
36
+ visitor.lowlink.set(v, visitor.index);
37
+ visitor.index++;
38
+ visitor.stack.push(v);
39
+ visitor.onStack.add(v);
40
+ }
41
+ function relaxEdge(visitor, v, w, recurse) {
42
+ if (!visitor.idx.has(w)) {
43
+ recurse(w);
44
+ visitor.lowlink.set(v, Math.min(visitor.lowlink.get(v) ?? 0, visitor.lowlink.get(w) ?? 0));
45
+ }
46
+ else if (visitor.onStack.has(w)) {
47
+ visitor.lowlink.set(v, Math.min(visitor.lowlink.get(v) ?? 0, visitor.idx.get(w) ?? 0));
48
+ }
49
+ }
50
+ function finaliseRoot(visitor, v) {
51
+ if ((visitor.lowlink.get(v) ?? 0) !== (visitor.idx.get(v) ?? 0))
52
+ return;
53
+ const component = [];
54
+ let w;
55
+ do {
56
+ w = visitor.stack.pop();
57
+ visitor.onStack.delete(w);
58
+ component.push(w);
59
+ } while (w !== v);
60
+ visitor.sccs.push(component);
61
+ }
62
+ /** Run Tarjan's SCC algorithm; returns the list of components in
63
+ * discovery order. Visitor state is hoisted to a named struct
64
+ * (`TarjanVisitor`) so the recursive body can be split into
65
+ * enterNode / relaxEdge / finaliseRoot without parameter sprawl. */
66
+ function runTarjan(nodeIds, adj) {
67
+ const visitor = createTarjanVisitor();
68
+ const strongconnect = (v) => {
69
+ enterNode(visitor, v);
70
+ for (const w of adj.get(v) ?? []) {
71
+ relaxEdge(visitor, v, w, strongconnect);
72
+ }
73
+ finaliseRoot(visitor, v);
74
+ };
75
+ for (const id of nodeIds) {
76
+ if (!visitor.idx.has(id))
77
+ strongconnect(id);
78
+ }
79
+ return visitor.sccs;
80
+ }
81
+ function summariseSccs(sccs, edges) {
82
+ const trivial = [];
83
+ const nonTrivial = [];
84
+ for (const scc of sccs) {
85
+ if (scc.length === 1)
86
+ trivial.push(scc[0]);
87
+ else
88
+ nonTrivial.push(...scc);
89
+ }
90
+ let condensationEdges = 0;
91
+ for (const e of edges) {
92
+ if (!sameScc(sccs, e.from, e.to))
93
+ condensationEdges++;
94
+ }
95
+ return {
96
+ sccCount: sccs.length,
97
+ trivialSCCs: trivial,
98
+ nonTrivialSCCs: nonTrivial,
99
+ condensationEdges
100
+ };
101
+ }
102
+ function sameScc(sccs, a, b) {
103
+ for (const scc of sccs) {
104
+ if (scc.includes(a) && scc.includes(b))
105
+ return true;
106
+ }
107
+ return false;
108
+ }
@@ -1,4 +1,4 @@
1
- import { type ProjectContext } from './project-context.js';
1
+ export { renderUiLibraryPriorityRule } from './standards-render.js';
2
2
  /**
3
3
  * Typed error raised when a planned `peaks standards` write target would
4
4
  * land outside `projectRoot` (or collide with the user-level baseline
@@ -107,14 +107,6 @@ export type ProjectStandardsInitOptions = {
107
107
  * the file-probe logic.
108
108
  */
109
109
  export declare function detectLanguage(projectRoot: string): StandardsLanguage;
110
- /**
111
- * UI library priority rule (effective 2026-08-01). Public so unit tests
112
- * can pin the contract without reaching through the plan builder.
113
- *
114
- * Returns the rule line, or `null` when the project has no detected
115
- * component library (in which case the rule is intentionally silent).
116
- */
117
- export declare function renderUiLibraryPriorityRule(ctx: ProjectContext): string | null;
118
110
  export declare function createProjectStandardsInitPlan(options: ProjectStandardsInitOptions): ProjectStandardsInitPlan;
119
111
  export declare function createProjectStandardsUpdatePlan(options: ProjectStandardsInitOptions): ProjectStandardsUpdatePlan;
120
112
  export declare function executeProjectStandardsInit(options: ProjectStandardsInitOptions): ProjectStandardsInitResult;
@@ -1,7 +1,9 @@
1
1
  import { closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readdirSync, realpathSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { homedir as osHomedir } from 'node:os';
3
3
  import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
4
- import { buildToolLabel, componentLibraryLabel, cssFrameworkLabel, detectProjectContext } from './project-context.js';
4
+ import { detectProjectContext } from './project-context.js';
5
+ import { renderClaudeMd, renderClaudeMdOneX, renderCodeReview, renderCommonCodingStyle, renderLanguageCodingStyle, renderManagedClaudeMdIndex, renderSecurity, } from './standards-render.js';
6
+ export { renderUiLibraryPriorityRule } from './standards-render.js';
5
7
  export class ProjectStandardsWriteTargetError extends Error {
6
8
  code = 'PROJECT_STANDARDS_WRITE_TARGET_OUTSIDE_ROOT';
7
9
  filePath;
@@ -106,213 +108,6 @@ function detectLanguageInternal(projectRoot) {
106
108
  export function detectLanguage(projectRoot) {
107
109
  return detectLanguageInternal(projectRoot);
108
110
  }
109
- function renderHeader(title) {
110
- return [
111
- `# ${title}`,
112
- '',
113
- 'Source: Peaks curated baseline; everything-claude-code reference: https://github.com/affaan-m/everything-claude-code',
114
- 'Scope: project-local standards for peaks-rd, peaks-qa, and peaks-code workflow preflight.',
115
- ''
116
- ].join('\n');
117
- }
118
- function renderProjectStackSection(ctx) {
119
- if (!ctx.hasPackageJson)
120
- return '';
121
- const lines = ['## Detected project stack', ''];
122
- lines.push(`- Build tool: ${buildToolLabel(ctx.buildTool)}${ctx.buildConfigPath !== undefined ? ` (\`${ctx.buildConfigPath}\`)` : ''}`);
123
- lines.push(`- Component library: ${componentLibraryLabel(ctx.componentLibrary)}`);
124
- if (ctx.cssFrameworks.length > 0) {
125
- lines.push(`- CSS: ${ctx.cssFrameworks.map(cssFrameworkLabel).join(', ')}`);
126
- }
127
- if (ctx.stateManagement.length > 0)
128
- lines.push(`- State management: ${ctx.stateManagement.join(', ')}`);
129
- if (ctx.routing.length > 0)
130
- lines.push(`- Routing: ${ctx.routing.join(', ')}`);
131
- if (ctx.dataFetching.length > 0)
132
- lines.push(`- Data fetching: ${ctx.dataFetching.join(', ')}`);
133
- if (ctx.notableDeps.length > 0)
134
- lines.push(`- Notable deps: ${ctx.notableDeps.join(', ')}`);
135
- lines.push('');
136
- if (ctx.cssConflicts.length > 0) {
137
- lines.push('## CSS framework conflicts', '');
138
- for (const conflict of ctx.cssConflicts)
139
- lines.push(`- ${conflict}`);
140
- lines.push('');
141
- }
142
- if (ctx.legacySignals.length > 0) {
143
- lines.push('## Legacy constraints (preserve for new code in the same modules)', '');
144
- for (const signal of ctx.legacySignals)
145
- lines.push(`- ${signal}`);
146
- lines.push('');
147
- }
148
- return lines.join('\n');
149
- }
150
- function renderClaudeMd(language, ctx) {
151
- const head = [
152
- '# Project Instructions',
153
- '',
154
- '> 🤖 AI 生成,请审阅',
155
- '',
156
- 'This repository uses project-local Peaks standards. Existing repository conventions override generic generated guidance.',
157
- '',
158
- 'Peaks workflow automation:',
159
- '- peaks-rd checks these standards before RD planning or implementation work.',
160
- '- peaks-qa checks code review and security guidance before verification work.',
161
- '- peaks-code summarizes RD and QA standards preflight before end-to-end code workflows.',
162
- '',
163
- 'Rules:',
164
- '- Read `.peaks/standards/common/coding-style.md` before editing code.',
165
- '- Read `.peaks/standards/common/code-review.md` before reviewing changes.',
166
- '- Read `.peaks/standards/common/security.md` before touching filesystem, user input, external calls, auth, or secrets.',
167
- `- Read .peaks/standards/${language}/coding-style.md for language-specific standards when applicable.`,
168
- '',
169
- 'Active Peaks-Loop skill presence: at the start of every response, run `peaks skill presence --json` to read the active skill marker. The CLI handles canonical-path resolution (`.peaks/_runtime/active-skill.json` with back-compat fallback to `.peaks/.active-skill.json`); do not read those files directly. When the response includes a valid skill name, display the compact status header: `Peaks-Loop Skill: <skill> | Peaks-Loop Gate: <gate> | Next: <one short action>`. Display the header on every turn while the CLI returns an active skill; omit when the CLI returns no active skill.'
170
- ].join('\n');
171
- const stack = renderProjectStackSection(ctx);
172
- return stack === '' ? head : `${head}\n${stack}`;
173
- }
174
- function renderCommonCodingStyle(ctx) {
175
- const baseRules = [
176
- '- Prefer simple, readable code over clever abstractions.',
177
- '- Keep functions focused and files cohesive.',
178
- '- Use immutable updates unless a language-specific convention explicitly favors mutation.',
179
- '- Validate user input, external data, file paths, and configuration at system boundaries.',
180
- '- Preserve existing project conventions when they are stricter than this baseline.'
181
- ];
182
- const stackRules = [];
183
- const lib = ctx.componentLibrary.name;
184
- if (lib === 'antd' || lib === 'antd-pro') {
185
- const major = ctx.componentLibrary.majorVersion ?? '5';
186
- stackRules.push(`- Use existing antd v${major} components (\`Button\`, \`Form\`, \`Table\`, \`Modal\`, \`Select\`). Never mix antd v3/v4/v5 APIs.`);
187
- stackRules.push(`- Customize antd via \`theme.token\` / \`ConfigProvider\` / \`className\` / \`styles\`. Do NOT apply TailwindCSS utility classes directly to antd components.`);
188
- if (ctx.componentLibrary.hasProSuite === true) {
189
- stackRules.push('- Use `@ant-design/pro-components` (`ProTable`, `ProForm`, `ProLayout`) where the page is already pro-based — do not introduce a parallel non-pro table/form.');
190
- }
191
- }
192
- if (lib === 'mui')
193
- stackRules.push('- Style MUI via `sx`, `styled()`, and `theme`. Do NOT apply TailwindCSS utility classes directly to MUI components.');
194
- if (ctx.cssFrameworks.includes('tailwind') && (lib === 'antd' || lib === 'antd-pro' || lib === 'mui')) {
195
- stackRules.push('- TailwindCSS is for layout/utility only; component-library tokens own component styling.');
196
- }
197
- if (ctx.cssFrameworks.includes('less'))
198
- stackRules.push('- Less variables in `src/theme/*.less` (or equivalent) are the canonical design tokens — extend them, do not hardcode colors/spacing.');
199
- if (ctx.stateManagement.length > 0)
200
- stackRules.push(`- Follow the existing state library (${ctx.stateManagement.join(', ')}); do not introduce a competing state library.`);
201
- if (ctx.dataFetching.length > 0)
202
- stackRules.push(`- Reuse the existing data-fetching pattern (${ctx.dataFetching.join(', ')}) for new API calls.`);
203
- for (const signal of ctx.legacySignals)
204
- stackRules.push(`- ${signal}`);
205
- const rules = stackRules.length > 0 ? [...baseRules, '', '## Project-specific rules', ...stackRules] : baseRules;
206
- return `${renderHeader('Common Coding Standards')}${rules.join('\n')}\n`;
207
- }
208
- function renderCodeReview(ctx) {
209
- const baseRules = [
210
- '- Review diffs for correctness, maintainability, test coverage, and regression risk.',
211
- '- Treat missing tests for changed behavior as a blocker unless the change is documentation-only.',
212
- '- Verify code paths that handle filesystem, external APIs, credentials, user input, or generated artifacts.',
213
- '- peaks-qa must use this guidance as part of code workflow preflight and final verification.'
214
- ];
215
- const extra = [];
216
- const lib = ctx.componentLibrary.name;
217
- if (lib === 'antd' || lib === 'antd-pro') {
218
- extra.push('- Block PRs that introduce a second component library (MUI/Chakra) alongside antd.');
219
- extra.push('- Block PRs that import antd v3/v4 APIs in this v5 project, or vice versa.');
220
- }
221
- if (ctx.cssFrameworks.includes('tailwind') && (lib === 'antd' || lib === 'antd-pro' || lib === 'mui')) {
222
- extra.push('- Flag Tailwind utility classes applied directly to component-library primitives; require component-library APIs instead.');
223
- }
224
- if (ctx.legacySignals.length > 0) {
225
- extra.push('- Verify new code in legacy modules preserves the existing patterns (see `.claude/rules/common/coding-style.md` "Project-specific rules").');
226
- }
227
- const rules = extra.length > 0 ? [...baseRules, '', '## Project-specific review focus', ...extra] : baseRules;
228
- return `${renderHeader('Code Review Standards')}${rules.join('\n')}\n`;
229
- }
230
- function renderSecurity(ctx) {
231
- const baseRules = [
232
- '- Never hardcode secrets, API keys, passwords, tokens, or credentials.',
233
- '- Do not send private code or secrets to external services without explicit user authorization.',
234
- '- Guard filesystem writes against path traversal, symlink, and junction escapes.',
235
- '- Require explicit confirmation for destructive actions, external state changes, and credential use.'
236
- ];
237
- const extra = [];
238
- if (ctx.buildTool === 'next')
239
- extra.push('- Validate request body / query / params at every API route boundary (`pages/api/**` or `app/api/**`).');
240
- if (ctx.dataFetching.length > 0)
241
- extra.push(`- Sanitize and validate API responses before rendering or persisting (current fetchers: ${ctx.dataFetching.join(', ')}).`);
242
- if (ctx.notableDeps.includes('monaco-editor') || ctx.notableDeps.includes('@monaco-editor/react')) {
243
- extra.push('- Monaco editor content is untrusted; never `eval` or `Function`-construct user-authored code without an explicit, reviewed sandbox.');
244
- }
245
- const rules = extra.length > 0 ? [...baseRules, '', '## Project-specific security focus', ...extra] : baseRules;
246
- return `${renderHeader('Security Review Standards')}${rules.join('\n')}\n`;
247
- }
248
- /**
249
- * UI library priority rule (effective 2026-08-01). Public so unit tests
250
- * can pin the contract without reaching through the plan builder.
251
- *
252
- * Returns the rule line, or `null` when the project has no detected
253
- * component library (in which case the rule is intentionally silent).
254
- */
255
- export function renderUiLibraryPriorityRule(ctx) {
256
- const lib = ctx.componentLibrary.name;
257
- const UI_LIB_NAMES = [
258
- 'antd', 'antd-pro', 'mui', 'shadcn',
259
- 'chakra', 'element-plus', 'element-ui',
260
- 'arco', 'tdesign', 'semi', 'nextui'
261
- ];
262
- if (!UI_LIB_NAMES.includes(lib)) {
263
- return null;
264
- }
265
- return `- UI library priority (peaks-loop, effective 2026-08-01): this project uses \`${lib}\`. Prefer the library's exported components over hand-rolled native DOM / HTML primitives. Native DOM is acceptable only for primitives the library does not ship; leave a one-line comment naming the library primitive that was unavailable. Library themes and tokens are the source of truth; do NOT introduce a parallel CSS framework or inline styles that fight the library.`;
266
- }
267
- function renderLanguageCodingStyle(language, ctx) {
268
- const languageName = language === 'generic' ? 'Generic' : language[0].toUpperCase() + language.slice(1);
269
- const typeSafetyRule = language === 'typescript' || language === 'javascript'
270
- ? '- Do not add new `any` types; use explicit domain types, generics, or `unknown` with narrowing.\n'
271
- : '';
272
- const baseRules = [
273
- `- Apply project-local conventions before generic ${language} guidance.`,
274
- `- Keep public APIs typed or documented according to ${language} ecosystem norms.`,
275
- typeSafetyRule.trim() !== '' ? typeSafetyRule.trim() : null,
276
- '- Prefer standard tooling and existing project scripts for formatting, linting, tests, and coverage.',
277
- `- peaks-rd must check this file before planning code changes in ${language} projects.`
278
- ].filter((line) => line !== null);
279
- const extra = [];
280
- if ((language === 'typescript' || language === 'javascript') && (ctx.componentLibrary.name === 'antd' || ctx.componentLibrary.name === 'antd-pro')) {
281
- extra.push('- Type form values, table records, and API responses with named interfaces; do not rely on `Form.useForm()` inference for shared shapes.');
282
- }
283
- if ((language === 'typescript' || language === 'javascript') && ctx.dataFetching.includes('@tanstack/react-query')) {
284
- extra.push('- Declare query/mutation generics (`useQuery<TData, TError>`) so consumers get typed data.');
285
- }
286
- if ((language === 'typescript' || language === 'javascript') && ctx.buildTool === 'umi') {
287
- extra.push('- Use the project\'s existing service-layer pattern (`src/services/**`) for API calls; do not hand-roll `fetch` in components.');
288
- }
289
- // UI library priority (effective 2026-08-01). Applies to any project whose
290
- // scan identified one of the supported component libraries; the rule
291
- // is generated by the standards update so it ships to every downstream
292
- // project through `peaks standards init/update`.
293
- const uiRule = renderUiLibraryPriorityRule(ctx);
294
- if (uiRule !== null)
295
- extra.push(uiRule);
296
- const rules = extra.length > 0 ? [...baseRules, '', '## Project-specific rules', ...extra] : baseRules;
297
- return `${renderHeader(`${languageName} Coding Standards`)}${rules.join('\n')}\n`;
298
- }
299
- function renderManagedClaudeMdIndex(language) {
300
- return [
301
- '<!-- peaks-standards:index:start -->',
302
- '## Peaks Standards Index',
303
- '- Constitution: `CLAUDE.md` is the repository-wide constitution.',
304
- '- Local laws: `.peaks/standards/**` are project-local laws and are created only when missing.',
305
- '- Managed by: `peaks standards update`.',
306
- '- Managed files:',
307
- ' - `.peaks/standards/common/code-review.md`',
308
- ' - `.peaks/standards/common/coding-style.md`',
309
- ' - `.peaks/standards/common/security.md`',
310
- ` - .peaks/standards/${language}/coding-style.md`,
311
- '- Conflict note: keep the existing body unchanged and resolve any disagreement manually before the next standards update.',
312
- '<!-- peaks-standards:index:end -->',
313
- ''
314
- ].join('\n');
315
- }
316
111
  function readFileIfExists(path) {
317
112
  if (!existsSync(path))
318
113
  return null;
@@ -425,30 +220,6 @@ function createLegacyOneXTemplates(language, ctx) {
425
220
  { relativePath: `.claude/rules/${language}/coding-style.md`, content: renderLanguageCodingStyle(language, ctx) }
426
221
  ];
427
222
  }
428
- function renderClaudeMdOneX(language, ctx) {
429
- const head = [
430
- '# Project Instructions',
431
- '',
432
- '> 🤖 AI 生成,请审阅',
433
- '',
434
- 'This repository uses project-local Peaks standards. Existing repository conventions override generic generated guidance.',
435
- '',
436
- 'Peaks workflow automation:',
437
- '- peaks-rd checks these standards before RD planning or implementation work.',
438
- '- peaks-qa checks code review and security guidance before verification work.',
439
- '- peaks-code summarizes RD and QA standards preflight before end-to-end code workflows.',
440
- '',
441
- 'Rules:',
442
- '- Read `.claude/rules/common/coding-style.md` before editing code.',
443
- '- Read `.claude/rules/common/code-review.md` before reviewing changes.',
444
- '- Read `.claude/rules/common/security.md` before touching filesystem, user input, external calls, auth, or secrets.',
445
- `- Read .claude/rules/${language}/coding-style.md for language-specific standards when applicable.`,
446
- '',
447
- 'Active Peaks-Loop skill presence: at the start of every response, run `peaks skill presence --json` to read the active skill marker. The CLI handles canonical-path resolution (`.peaks/_runtime/active-skill.json` with back-compat fallback to `.peaks/.active-skill.json`); do not read those files directly. When the response includes a valid skill name, display the compact status header: `Peaks-Loop Skill: <skill> | Peaks-Loop Gate: <gate> | Next: <one short action>`. Display the header on every turn while the CLI returns an active skill; omit when the CLI returns no active skill.'
448
- ].join('\n');
449
- const stack = renderProjectStackSection(ctx);
450
- return stack === '' ? head : `${head}\n${stack}`;
451
- }
452
223
  /**
453
224
  * Detect whether the consumer project still carries a 1.x "thick"
454
225
  * `.claude/rules/` tree (the legacy layout that 2.0 migrates away
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Pure render / template functions extracted from
3
+ * `project-standards-service.ts` (slice 2026-09-06-split-batch-b) so the
4
+ * service stays under the 800-line cap. Behaviour-preserving verbatim move;
5
+ * `export` keywords were added to the symbols the service still imports.
6
+ */
7
+ import { type ProjectContext } from './project-context.js';
8
+ import type { StandardsLanguage } from './project-standards-service.js';
9
+ export declare function renderClaudeMd(language: StandardsLanguage, ctx: ProjectContext): string;
10
+ export declare function renderCommonCodingStyle(ctx: ProjectContext): string;
11
+ export declare function renderCodeReview(ctx: ProjectContext): string;
12
+ export declare function renderSecurity(ctx: ProjectContext): string;
13
+ /**
14
+ * UI library priority rule (effective 2026-08-01). Public so unit tests
15
+ * can pin the contract without reaching through the plan builder.
16
+ *
17
+ * Returns the rule line, or `null` when the project has no detected
18
+ * component library (in which case the rule is intentionally silent).
19
+ */
20
+ export declare function renderUiLibraryPriorityRule(ctx: ProjectContext): string | null;
21
+ export declare function renderLanguageCodingStyle(language: StandardsLanguage, ctx: ProjectContext): string;
22
+ export declare function renderManagedClaudeMdIndex(language: StandardsLanguage): string;
23
+ export declare function renderClaudeMdOneX(language: StandardsLanguage, ctx: ProjectContext): string;