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.
- package/CHANGELOG.md +27 -0
- package/dist/cli/commands/_register.js +4 -0
- package/dist/cli/commands/dispatch-commands.d.ts +5 -41
- package/dist/cli/commands/dispatch-commands.js +35 -185
- package/dist/cli/commands/evidence-commands.d.ts +12 -0
- package/dist/cli/commands/evidence-commands.js +43 -0
- package/dist/cli/commands/fresh-context-commands.d.ts +15 -0
- package/dist/cli/commands/fresh-context-commands.js +24 -0
- package/dist/cli/commands/request-commands.js +2 -96
- package/dist/cli/commands/request-format-helpers.d.ts +19 -0
- package/dist/cli/commands/request-format-helpers.js +102 -0
- package/dist/cli/commands/workspace/init-command.js +27 -14
- package/dist/cli/commands/worktree-auth-commands.js +2 -472
- package/dist/cli/commands/worktree-lease-commands.d.ts +21 -0
- package/dist/cli/commands/worktree-lease-commands.js +500 -0
- package/dist/services/artifacts/request-artifact-service.js +13 -3
- package/dist/services/code/auto-compact-lifecycle.d.ts +102 -0
- package/dist/services/code/auto-compact-lifecycle.js +235 -0
- package/dist/services/code/auto-compact-orchestrator.d.ts +1 -1
- package/dist/services/code/auto-compact-orchestrator.js +1 -222
- package/dist/services/codegraph/codegraph-autorefresh.js +44 -6
- package/dist/services/codegraph/codegraph-preflight-service.d.ts +5 -2
- package/dist/services/codegraph/codegraph-preflight-service.js +13 -6
- package/dist/services/codegraph/codegraph-service.d.ts +17 -2
- package/dist/services/codegraph/codegraph-service.js +27 -2
- package/dist/services/context/build-dispatch-system-prompt.d.ts +26 -0
- package/dist/services/context/build-dispatch-system-prompt.js +38 -3
- package/dist/services/dispatch/dispatch-record-types.d.ts +278 -0
- package/dist/services/dispatch/dispatch-record-types.js +15 -0
- package/dist/services/dispatch/dispatch-record-upgrade.d.ts +5 -0
- package/dist/services/dispatch/dispatch-record-upgrade.js +230 -0
- package/dist/services/dispatch/dispatch-record-writer.d.ts +3 -278
- package/dist/services/dispatch/dispatch-record-writer.js +3 -245
- package/dist/services/dispatch/dispatch-sub-agent.d.ts +43 -0
- package/dist/services/dispatch/dispatch-sub-agent.js +55 -0
- package/dist/services/dispatch/isolation-lease.d.ts +43 -0
- package/dist/services/dispatch/isolation-lease.js +129 -0
- package/dist/services/evidence/evidence-generator.d.ts +26 -0
- package/dist/services/evidence/evidence-generator.js +349 -0
- package/dist/services/fresh-context/config.d.ts +1 -0
- package/dist/services/fresh-context/config.js +13 -0
- package/dist/services/fresh-context/fresh-context-block.d.ts +12 -0
- package/dist/services/fresh-context/fresh-context-block.js +40 -0
- package/dist/services/fresh-context/trigger-scan.d.ts +30 -0
- package/dist/services/fresh-context/trigger-scan.js +29 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +65 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +204 -0
- package/dist/services/skills/hooks-settings-service.d.ts +2 -64
- package/dist/services/skills/hooks-settings-service.js +2 -215
- package/dist/services/skills/skill-statusline-renderer.d.ts +2 -34
- package/dist/services/skills/skill-statusline-renderer.js +1 -184
- package/dist/services/skills/statusline-palette.d.ts +62 -0
- package/dist/services/skills/statusline-palette.js +190 -0
- package/dist/services/slice/slice-decompose-import-edges.d.ts +8 -0
- package/dist/services/slice/slice-decompose-import-edges.js +102 -0
- package/dist/services/slice/slice-decompose-service.js +3 -198
- package/dist/services/slice/slice-decompose-tarjan.d.ts +8 -0
- package/dist/services/slice/slice-decompose-tarjan.js +108 -0
- package/dist/services/standards/project-standards-service.d.ts +1 -9
- package/dist/services/standards/project-standards-service.js +3 -232
- package/dist/services/standards/standards-render.d.ts +23 -0
- package/dist/services/standards/standards-render.js +238 -0
- package/dist/services/standards/ui-library-dispatch-block.d.ts +27 -0
- package/dist/services/standards/ui-library-dispatch-block.js +48 -0
- package/dist/services/workspace/reconcile-migrate.d.ts +77 -0
- package/dist/services/workspace/reconcile-migrate.js +230 -0
- package/dist/services/workspace/reconcile-service.d.ts +0 -72
- package/dist/services/workspace/reconcile-service.js +3 -220
- package/dist/services/workspace/workspace-service.js +13 -2
- package/dist/shared/incrementing-number.d.ts +11 -0
- package/dist/shared/incrementing-number.js +16 -3
- package/package.json +5 -5
- package/skills/peaks-code/SKILL.md +6 -2
- package/skills/peaks-code/references/fresh-context-preflight.md +81 -0
- package/skills/peaks-code/references/sub-agent-dispatch.md +27 -8
|
@@ -0,0 +1,238 @@
|
|
|
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 { buildToolLabel, componentLibraryLabel, cssFrameworkLabel } from './project-context.js';
|
|
8
|
+
function renderHeader(title) {
|
|
9
|
+
return [
|
|
10
|
+
`# ${title}`,
|
|
11
|
+
'',
|
|
12
|
+
'Source: Peaks curated baseline; everything-claude-code reference: https://github.com/affaan-m/everything-claude-code',
|
|
13
|
+
'Scope: project-local standards for peaks-rd, peaks-qa, and peaks-code workflow preflight.',
|
|
14
|
+
''
|
|
15
|
+
].join('\n');
|
|
16
|
+
}
|
|
17
|
+
function renderProjectStackSection(ctx) {
|
|
18
|
+
if (!ctx.hasPackageJson)
|
|
19
|
+
return '';
|
|
20
|
+
const lines = ['## Detected project stack', ''];
|
|
21
|
+
lines.push(`- Build tool: ${buildToolLabel(ctx.buildTool)}${ctx.buildConfigPath !== undefined ? ` (\`${ctx.buildConfigPath}\`)` : ''}`);
|
|
22
|
+
lines.push(`- Component library: ${componentLibraryLabel(ctx.componentLibrary)}`);
|
|
23
|
+
if (ctx.cssFrameworks.length > 0) {
|
|
24
|
+
lines.push(`- CSS: ${ctx.cssFrameworks.map(cssFrameworkLabel).join(', ')}`);
|
|
25
|
+
}
|
|
26
|
+
if (ctx.stateManagement.length > 0)
|
|
27
|
+
lines.push(`- State management: ${ctx.stateManagement.join(', ')}`);
|
|
28
|
+
if (ctx.routing.length > 0)
|
|
29
|
+
lines.push(`- Routing: ${ctx.routing.join(', ')}`);
|
|
30
|
+
if (ctx.dataFetching.length > 0)
|
|
31
|
+
lines.push(`- Data fetching: ${ctx.dataFetching.join(', ')}`);
|
|
32
|
+
if (ctx.notableDeps.length > 0)
|
|
33
|
+
lines.push(`- Notable deps: ${ctx.notableDeps.join(', ')}`);
|
|
34
|
+
lines.push('');
|
|
35
|
+
if (ctx.cssConflicts.length > 0) {
|
|
36
|
+
lines.push('## CSS framework conflicts', '');
|
|
37
|
+
for (const conflict of ctx.cssConflicts)
|
|
38
|
+
lines.push(`- ${conflict}`);
|
|
39
|
+
lines.push('');
|
|
40
|
+
}
|
|
41
|
+
if (ctx.legacySignals.length > 0) {
|
|
42
|
+
lines.push('## Legacy constraints (preserve for new code in the same modules)', '');
|
|
43
|
+
for (const signal of ctx.legacySignals)
|
|
44
|
+
lines.push(`- ${signal}`);
|
|
45
|
+
lines.push('');
|
|
46
|
+
}
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
|
49
|
+
export function renderClaudeMd(language, ctx) {
|
|
50
|
+
const head = [
|
|
51
|
+
'# Project Instructions',
|
|
52
|
+
'',
|
|
53
|
+
'> 🤖 AI 生成,请审阅',
|
|
54
|
+
'',
|
|
55
|
+
'This repository uses project-local Peaks standards. Existing repository conventions override generic generated guidance.',
|
|
56
|
+
'',
|
|
57
|
+
'Peaks workflow automation:',
|
|
58
|
+
'- peaks-rd checks these standards before RD planning or implementation work.',
|
|
59
|
+
'- peaks-qa checks code review and security guidance before verification work.',
|
|
60
|
+
'- peaks-code summarizes RD and QA standards preflight before end-to-end code workflows.',
|
|
61
|
+
'',
|
|
62
|
+
'Rules:',
|
|
63
|
+
'- Read `.peaks/standards/common/coding-style.md` before editing code.',
|
|
64
|
+
'- Read `.peaks/standards/common/code-review.md` before reviewing changes.',
|
|
65
|
+
'- Read `.peaks/standards/common/security.md` before touching filesystem, user input, external calls, auth, or secrets.',
|
|
66
|
+
`- Read .peaks/standards/${language}/coding-style.md for language-specific standards when applicable.`,
|
|
67
|
+
'',
|
|
68
|
+
'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.'
|
|
69
|
+
].join('\n');
|
|
70
|
+
const stack = renderProjectStackSection(ctx);
|
|
71
|
+
return stack === '' ? head : `${head}\n${stack}`;
|
|
72
|
+
}
|
|
73
|
+
export function renderCommonCodingStyle(ctx) {
|
|
74
|
+
const baseRules = [
|
|
75
|
+
'- Prefer simple, readable code over clever abstractions.',
|
|
76
|
+
'- Keep functions focused and files cohesive.',
|
|
77
|
+
'- Use immutable updates unless a language-specific convention explicitly favors mutation.',
|
|
78
|
+
'- Validate user input, external data, file paths, and configuration at system boundaries.',
|
|
79
|
+
'- Preserve existing project conventions when they are stricter than this baseline.'
|
|
80
|
+
];
|
|
81
|
+
const stackRules = [];
|
|
82
|
+
const lib = ctx.componentLibrary.name;
|
|
83
|
+
if (lib === 'antd' || lib === 'antd-pro') {
|
|
84
|
+
const major = ctx.componentLibrary.majorVersion ?? '5';
|
|
85
|
+
stackRules.push(`- Use existing antd v${major} components (\`Button\`, \`Form\`, \`Table\`, \`Modal\`, \`Select\`). Never mix antd v3/v4/v5 APIs.`);
|
|
86
|
+
stackRules.push(`- Customize antd via \`theme.token\` / \`ConfigProvider\` / \`className\` / \`styles\`. Do NOT apply TailwindCSS utility classes directly to antd components.`);
|
|
87
|
+
if (ctx.componentLibrary.hasProSuite === true) {
|
|
88
|
+
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.');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (lib === 'mui')
|
|
92
|
+
stackRules.push('- Style MUI via `sx`, `styled()`, and `theme`. Do NOT apply TailwindCSS utility classes directly to MUI components.');
|
|
93
|
+
if (ctx.cssFrameworks.includes('tailwind') && (lib === 'antd' || lib === 'antd-pro' || lib === 'mui')) {
|
|
94
|
+
stackRules.push('- TailwindCSS is for layout/utility only; component-library tokens own component styling.');
|
|
95
|
+
}
|
|
96
|
+
if (ctx.cssFrameworks.includes('less'))
|
|
97
|
+
stackRules.push('- Less variables in `src/theme/*.less` (or equivalent) are the canonical design tokens — extend them, do not hardcode colors/spacing.');
|
|
98
|
+
if (ctx.stateManagement.length > 0)
|
|
99
|
+
stackRules.push(`- Follow the existing state library (${ctx.stateManagement.join(', ')}); do not introduce a competing state library.`);
|
|
100
|
+
if (ctx.dataFetching.length > 0)
|
|
101
|
+
stackRules.push(`- Reuse the existing data-fetching pattern (${ctx.dataFetching.join(', ')}) for new API calls.`);
|
|
102
|
+
for (const signal of ctx.legacySignals)
|
|
103
|
+
stackRules.push(`- ${signal}`);
|
|
104
|
+
const rules = stackRules.length > 0 ? [...baseRules, '', '## Project-specific rules', ...stackRules] : baseRules;
|
|
105
|
+
return `${renderHeader('Common Coding Standards')}${rules.join('\n')}\n`;
|
|
106
|
+
}
|
|
107
|
+
export function renderCodeReview(ctx) {
|
|
108
|
+
const baseRules = [
|
|
109
|
+
'- Review diffs for correctness, maintainability, test coverage, and regression risk.',
|
|
110
|
+
'- Treat missing tests for changed behavior as a blocker unless the change is documentation-only.',
|
|
111
|
+
'- Verify code paths that handle filesystem, external APIs, credentials, user input, or generated artifacts.',
|
|
112
|
+
'- peaks-qa must use this guidance as part of code workflow preflight and final verification.'
|
|
113
|
+
];
|
|
114
|
+
const extra = [];
|
|
115
|
+
const lib = ctx.componentLibrary.name;
|
|
116
|
+
if (lib === 'antd' || lib === 'antd-pro') {
|
|
117
|
+
extra.push('- Block PRs that introduce a second component library (MUI/Chakra) alongside antd.');
|
|
118
|
+
extra.push('- Block PRs that import antd v3/v4 APIs in this v5 project, or vice versa.');
|
|
119
|
+
}
|
|
120
|
+
if (ctx.cssFrameworks.includes('tailwind') && (lib === 'antd' || lib === 'antd-pro' || lib === 'mui')) {
|
|
121
|
+
extra.push('- Flag Tailwind utility classes applied directly to component-library primitives; require component-library APIs instead.');
|
|
122
|
+
}
|
|
123
|
+
if (ctx.legacySignals.length > 0) {
|
|
124
|
+
extra.push('- Verify new code in legacy modules preserves the existing patterns (see `.claude/rules/common/coding-style.md` "Project-specific rules").');
|
|
125
|
+
}
|
|
126
|
+
const rules = extra.length > 0 ? [...baseRules, '', '## Project-specific review focus', ...extra] : baseRules;
|
|
127
|
+
return `${renderHeader('Code Review Standards')}${rules.join('\n')}\n`;
|
|
128
|
+
}
|
|
129
|
+
export function renderSecurity(ctx) {
|
|
130
|
+
const baseRules = [
|
|
131
|
+
'- Never hardcode secrets, API keys, passwords, tokens, or credentials.',
|
|
132
|
+
'- Do not send private code or secrets to external services without explicit user authorization.',
|
|
133
|
+
'- Guard filesystem writes against path traversal, symlink, and junction escapes.',
|
|
134
|
+
'- Require explicit confirmation for destructive actions, external state changes, and credential use.'
|
|
135
|
+
];
|
|
136
|
+
const extra = [];
|
|
137
|
+
if (ctx.buildTool === 'next')
|
|
138
|
+
extra.push('- Validate request body / query / params at every API route boundary (`pages/api/**` or `app/api/**`).');
|
|
139
|
+
if (ctx.dataFetching.length > 0)
|
|
140
|
+
extra.push(`- Sanitize and validate API responses before rendering or persisting (current fetchers: ${ctx.dataFetching.join(', ')}).`);
|
|
141
|
+
if (ctx.notableDeps.includes('monaco-editor') || ctx.notableDeps.includes('@monaco-editor/react')) {
|
|
142
|
+
extra.push('- Monaco editor content is untrusted; never `eval` or `Function`-construct user-authored code without an explicit, reviewed sandbox.');
|
|
143
|
+
}
|
|
144
|
+
const rules = extra.length > 0 ? [...baseRules, '', '## Project-specific security focus', ...extra] : baseRules;
|
|
145
|
+
return `${renderHeader('Security Review Standards')}${rules.join('\n')}\n`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* UI library priority rule (effective 2026-08-01). Public so unit tests
|
|
149
|
+
* can pin the contract without reaching through the plan builder.
|
|
150
|
+
*
|
|
151
|
+
* Returns the rule line, or `null` when the project has no detected
|
|
152
|
+
* component library (in which case the rule is intentionally silent).
|
|
153
|
+
*/
|
|
154
|
+
export function renderUiLibraryPriorityRule(ctx) {
|
|
155
|
+
const lib = ctx.componentLibrary.name;
|
|
156
|
+
const UI_LIB_NAMES = [
|
|
157
|
+
'antd', 'antd-pro', 'mui', 'shadcn',
|
|
158
|
+
'chakra', 'element-plus', 'element-ui',
|
|
159
|
+
'arco', 'tdesign', 'semi', 'nextui'
|
|
160
|
+
];
|
|
161
|
+
if (!UI_LIB_NAMES.includes(lib)) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
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.`;
|
|
165
|
+
}
|
|
166
|
+
export function renderLanguageCodingStyle(language, ctx) {
|
|
167
|
+
const languageName = language === 'generic' ? 'Generic' : language[0].toUpperCase() + language.slice(1);
|
|
168
|
+
const typeSafetyRule = language === 'typescript' || language === 'javascript'
|
|
169
|
+
? '- Do not add new `any` types; use explicit domain types, generics, or `unknown` with narrowing.\n'
|
|
170
|
+
: '';
|
|
171
|
+
const baseRules = [
|
|
172
|
+
`- Apply project-local conventions before generic ${language} guidance.`,
|
|
173
|
+
`- Keep public APIs typed or documented according to ${language} ecosystem norms.`,
|
|
174
|
+
typeSafetyRule.trim() !== '' ? typeSafetyRule.trim() : null,
|
|
175
|
+
'- Prefer standard tooling and existing project scripts for formatting, linting, tests, and coverage.',
|
|
176
|
+
`- peaks-rd must check this file before planning code changes in ${language} projects.`
|
|
177
|
+
].filter((line) => line !== null);
|
|
178
|
+
const extra = [];
|
|
179
|
+
if ((language === 'typescript' || language === 'javascript') && (ctx.componentLibrary.name === 'antd' || ctx.componentLibrary.name === 'antd-pro')) {
|
|
180
|
+
extra.push('- Type form values, table records, and API responses with named interfaces; do not rely on `Form.useForm()` inference for shared shapes.');
|
|
181
|
+
}
|
|
182
|
+
if ((language === 'typescript' || language === 'javascript') && ctx.dataFetching.includes('@tanstack/react-query')) {
|
|
183
|
+
extra.push('- Declare query/mutation generics (`useQuery<TData, TError>`) so consumers get typed data.');
|
|
184
|
+
}
|
|
185
|
+
if ((language === 'typescript' || language === 'javascript') && ctx.buildTool === 'umi') {
|
|
186
|
+
extra.push('- Use the project\'s existing service-layer pattern (`src/services/**`) for API calls; do not hand-roll `fetch` in components.');
|
|
187
|
+
}
|
|
188
|
+
// UI library priority (effective 2026-08-01). Applies to any project whose
|
|
189
|
+
// scan identified one of the supported component libraries; the rule
|
|
190
|
+
// is generated by the standards update so it ships to every downstream
|
|
191
|
+
// project through `peaks standards init/update`.
|
|
192
|
+
const uiRule = renderUiLibraryPriorityRule(ctx);
|
|
193
|
+
if (uiRule !== null)
|
|
194
|
+
extra.push(uiRule);
|
|
195
|
+
const rules = extra.length > 0 ? [...baseRules, '', '## Project-specific rules', ...extra] : baseRules;
|
|
196
|
+
return `${renderHeader(`${languageName} Coding Standards`)}${rules.join('\n')}\n`;
|
|
197
|
+
}
|
|
198
|
+
export function renderManagedClaudeMdIndex(language) {
|
|
199
|
+
return [
|
|
200
|
+
'<!-- peaks-standards:index:start -->',
|
|
201
|
+
'## Peaks Standards Index',
|
|
202
|
+
'- Constitution: `CLAUDE.md` is the repository-wide constitution.',
|
|
203
|
+
'- Local laws: `.peaks/standards/**` are project-local laws and are created only when missing.',
|
|
204
|
+
'- Managed by: `peaks standards update`.',
|
|
205
|
+
'- Managed files:',
|
|
206
|
+
' - `.peaks/standards/common/code-review.md`',
|
|
207
|
+
' - `.peaks/standards/common/coding-style.md`',
|
|
208
|
+
' - `.peaks/standards/common/security.md`',
|
|
209
|
+
` - .peaks/standards/${language}/coding-style.md`,
|
|
210
|
+
'- Conflict note: keep the existing body unchanged and resolve any disagreement manually before the next standards update.',
|
|
211
|
+
'<!-- peaks-standards:index:end -->',
|
|
212
|
+
''
|
|
213
|
+
].join('\n');
|
|
214
|
+
}
|
|
215
|
+
export function renderClaudeMdOneX(language, ctx) {
|
|
216
|
+
const head = [
|
|
217
|
+
'# Project Instructions',
|
|
218
|
+
'',
|
|
219
|
+
'> 🤖 AI 生成,请审阅',
|
|
220
|
+
'',
|
|
221
|
+
'This repository uses project-local Peaks standards. Existing repository conventions override generic generated guidance.',
|
|
222
|
+
'',
|
|
223
|
+
'Peaks workflow automation:',
|
|
224
|
+
'- peaks-rd checks these standards before RD planning or implementation work.',
|
|
225
|
+
'- peaks-qa checks code review and security guidance before verification work.',
|
|
226
|
+
'- peaks-code summarizes RD and QA standards preflight before end-to-end code workflows.',
|
|
227
|
+
'',
|
|
228
|
+
'Rules:',
|
|
229
|
+
'- Read `.claude/rules/common/coding-style.md` before editing code.',
|
|
230
|
+
'- Read `.claude/rules/common/code-review.md` before reviewing changes.',
|
|
231
|
+
'- Read `.claude/rules/common/security.md` before touching filesystem, user input, external calls, auth, or secrets.',
|
|
232
|
+
`- Read .claude/rules/${language}/coding-style.md for language-specific standards when applicable.`,
|
|
233
|
+
'',
|
|
234
|
+
'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.'
|
|
235
|
+
].join('\n');
|
|
236
|
+
const stack = renderProjectStackSection(ctx);
|
|
237
|
+
return stack === '' ? head : `${head}\n${stack}`;
|
|
238
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-09-06-ui-lib-dispatch-priority: render a `## Project stack`
|
|
3
|
+
* markdown block for the RD/UI sub-agent dispatch system prompt, and the
|
|
4
|
+
* dispatch-site convenience wrapper that computes it from a project root.
|
|
5
|
+
*
|
|
6
|
+
* Kept in its own small module so the block rendering does NOT grow
|
|
7
|
+
* `project-standards-service.ts` past the 800-line cap (the slice only
|
|
8
|
+
* surfaces the existing standards rule in a second surface — it does not
|
|
9
|
+
* add to the standards service itself).
|
|
10
|
+
*/
|
|
11
|
+
import { type ProjectContext } from './project-context.js';
|
|
12
|
+
/**
|
|
13
|
+
* Render the block. The library-first directive reuses
|
|
14
|
+
* `renderUiLibraryPriorityRule` (the single source of truth for that prose —
|
|
15
|
+
* no second copy to drift). Returns `null` when the project has no detected
|
|
16
|
+
* component library (or a library the rule does not cover), so the dispatch
|
|
17
|
+
* prompt degrades byte-identically to the legacy shape (no empty or dangling
|
|
18
|
+
* "Project stack" heading).
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderUiLibraryPriorityDispatchBlock(ctx: ProjectContext): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Dispatch-site convenience wrapper: detect the project context and render
|
|
23
|
+
* the block. Fail-soft — any detection error degrades to `null` (no block)
|
|
24
|
+
* so a bad package.json or unreadable source sample never hard-blocks the
|
|
25
|
+
* dispatch.
|
|
26
|
+
*/
|
|
27
|
+
export declare function computeUiLibraryDispatchBlock(projectRoot: string): string | null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-09-06-ui-lib-dispatch-priority: render a `## Project stack`
|
|
3
|
+
* markdown block for the RD/UI sub-agent dispatch system prompt, and the
|
|
4
|
+
* dispatch-site convenience wrapper that computes it from a project root.
|
|
5
|
+
*
|
|
6
|
+
* Kept in its own small module so the block rendering does NOT grow
|
|
7
|
+
* `project-standards-service.ts` past the 800-line cap (the slice only
|
|
8
|
+
* surfaces the existing standards rule in a second surface — it does not
|
|
9
|
+
* add to the standards service itself).
|
|
10
|
+
*/
|
|
11
|
+
import { buildToolLabel, componentLibraryLabel, cssFrameworkLabel, detectProjectContext, } from './project-context.js';
|
|
12
|
+
import { renderUiLibraryPriorityRule } from './project-standards-service.js';
|
|
13
|
+
/**
|
|
14
|
+
* Render the block. The library-first directive reuses
|
|
15
|
+
* `renderUiLibraryPriorityRule` (the single source of truth for that prose —
|
|
16
|
+
* no second copy to drift). Returns `null` when the project has no detected
|
|
17
|
+
* component library (or a library the rule does not cover), so the dispatch
|
|
18
|
+
* prompt degrades byte-identically to the legacy shape (no empty or dangling
|
|
19
|
+
* "Project stack" heading).
|
|
20
|
+
*/
|
|
21
|
+
export function renderUiLibraryPriorityDispatchBlock(ctx) {
|
|
22
|
+
const rule = renderUiLibraryPriorityRule(ctx);
|
|
23
|
+
if (rule === null)
|
|
24
|
+
return null;
|
|
25
|
+
const lines = ['## Project stack', ''];
|
|
26
|
+
lines.push(`- Component library: ${componentLibraryLabel(ctx.componentLibrary)}`);
|
|
27
|
+
lines.push(`- Build tool: ${buildToolLabel(ctx.buildTool)}`);
|
|
28
|
+
if (ctx.cssFrameworks.length > 0) {
|
|
29
|
+
lines.push(`- CSS: ${ctx.cssFrameworks.map(cssFrameworkLabel).join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
lines.push('');
|
|
32
|
+
lines.push(rule);
|
|
33
|
+
return lines.join('\n');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Dispatch-site convenience wrapper: detect the project context and render
|
|
37
|
+
* the block. Fail-soft — any detection error degrades to `null` (no block)
|
|
38
|
+
* so a bad package.json or unreadable source sample never hard-blocks the
|
|
39
|
+
* dispatch.
|
|
40
|
+
*/
|
|
41
|
+
export function computeUiLibraryDispatchBlock(projectRoot) {
|
|
42
|
+
try {
|
|
43
|
+
return renderUiLibraryPriorityDispatchBlock(detectProjectContext(projectRoot));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration helpers extracted from `reconcile-service.ts`
|
|
3
|
+
* (slice 2026-09-06-split-batch-b) so the service stays under the
|
|
4
|
+
* 800-line cap. Behaviour-preserving verbatim move.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* One-time migration step (added in slice 2026-06-05-peaks-runtime-layer).
|
|
8
|
+
*
|
|
9
|
+
* Move the legacy runtime files at:
|
|
10
|
+
* - `.peaks/.session.json`
|
|
11
|
+
* - `.peaks/.active-skill.json`
|
|
12
|
+
* - `.peaks/sop-state/`
|
|
13
|
+
* into their new canonical home at:
|
|
14
|
+
* - `.peaks/_runtime/session.json`
|
|
15
|
+
* - `.peaks/_runtime/active-skill.json`
|
|
16
|
+
* - `.peaks/_runtime/sop-state/`
|
|
17
|
+
*
|
|
18
|
+
* Behavior:
|
|
19
|
+
* - Idempotent: re-running on a tree that is already on the new
|
|
20
|
+
* layout produces `migratedFiles: []`.
|
|
21
|
+
* - Best-effort: uses `fs.renameSync` (atomic on POSIX, best-effort
|
|
22
|
+
* on Windows) and falls back to `copyFileSync` + `unlinkSync` if
|
|
23
|
+
* rename throws (e.g. cross-device move on Windows). Errors are
|
|
24
|
+
* collected per file and returned in the `errors` array so the
|
|
25
|
+
* reconcile envelope can surface them without blocking the rest of
|
|
26
|
+
* the migration.
|
|
27
|
+
* - Creates `.peaks/_runtime/` on demand if any of the old paths
|
|
28
|
+
* are present.
|
|
29
|
+
*
|
|
30
|
+
* @returns `{ migratedFiles, errors }`. `migratedFiles` lists the
|
|
31
|
+
* *old* relative paths (e.g. `.peaks/.session.json`) that were
|
|
32
|
+
* successfully moved, in move order. `errors` lists per-file
|
|
33
|
+
* failures with the old path and a human-readable message.
|
|
34
|
+
*/
|
|
35
|
+
export declare function migrateOldRuntimeState(projectRoot: string): {
|
|
36
|
+
migratedFiles: string[];
|
|
37
|
+
errors: Array<{
|
|
38
|
+
path: string;
|
|
39
|
+
message: string;
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* One-time sub-agent state migration (slice 2026-06-06-sub-agent-spawn-bug-and-decouple).
|
|
44
|
+
*
|
|
45
|
+
* Move the legacy per-session sub-agent state files at:
|
|
46
|
+
* - `.peaks/_runtime/<sid>/system/subagent-progress.json`
|
|
47
|
+
* - `.peaks/_runtime/<sid>/system/progress-spawn.json`
|
|
48
|
+
* into the new canonical home at:
|
|
49
|
+
* - `.peaks/_sub_agents/<sid>/subagent-progress.json`
|
|
50
|
+
* - `.peaks/_sub_agents/<sid>/progress-spawn.json`
|
|
51
|
+
*
|
|
52
|
+
* Behavior:
|
|
53
|
+
* - Idempotent: re-running on a tree that is already on the new layout
|
|
54
|
+
* produces `migratedFiles: []`.
|
|
55
|
+
* - Best-effort: uses `fs.renameSync` and falls back to `copyFileSync +
|
|
56
|
+
* unlinkSync` if rename throws (e.g. cross-device move on Windows).
|
|
57
|
+
* - Empty `<sid>/system/` dir removal (R-2 guard): the legacy `system/`
|
|
58
|
+
* subdir is only removed when it has zero other files, so a tree where
|
|
59
|
+
* the user had unrelated content in `system/` is left untouched.
|
|
60
|
+
* - New-path-wins: when both old and new files exist, the old file is
|
|
61
|
+
* removed (the new path is authoritative).
|
|
62
|
+
*
|
|
63
|
+
* Walks every discovered session — not just the canonical one — so a user
|
|
64
|
+
* with 6 pre-migration sessions gets all of them migrated in one reconcile
|
|
65
|
+
* pass.
|
|
66
|
+
*
|
|
67
|
+
* @returns `{ migratedFiles, errors }`. `migratedFiles` lists the *old*
|
|
68
|
+
* relative paths (e.g. `.peaks/_runtime/<sid>/system/subagent-progress.json`) that
|
|
69
|
+
* were successfully moved. `errors` lists per-file failures.
|
|
70
|
+
*/
|
|
71
|
+
export declare function migrateSubAgentState(projectRoot: string): {
|
|
72
|
+
migratedFiles: string[];
|
|
73
|
+
errors: Array<{
|
|
74
|
+
path: string;
|
|
75
|
+
message: string;
|
|
76
|
+
}>;
|
|
77
|
+
};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration helpers extracted from `reconcile-service.ts`
|
|
3
|
+
* (slice 2026-09-06-split-batch-b) so the service stays under the
|
|
4
|
+
* 800-line cap. Behaviour-preserving verbatim move.
|
|
5
|
+
*/
|
|
6
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, rmdirSync, unlinkSync } from 'node:fs';
|
|
7
|
+
import { dirname, join, resolve } from 'node:path';
|
|
8
|
+
// Call-time-only dependency on the sibling service: `migrateSubAgentState`
|
|
9
|
+
// walks every discovered session via `discoverSessions`, which is invoked
|
|
10
|
+
// only at call time (after both modules have fully evaluated), so the
|
|
11
|
+
// value-level circular import is safe under ESM live bindings.
|
|
12
|
+
import { discoverSessions } from './reconcile-service.js';
|
|
13
|
+
// Sub-agent state file basenames (slice 2026-06-06-sub-agent-spawn-bug-and-decouple).
|
|
14
|
+
// The legacy location was `.peaks/_runtime/<sid>/system/<filename>`; the canonical new
|
|
15
|
+
// location is `.peaks/_sub_agents/<sid>/<filename>`. `migrateSubAgentState`
|
|
16
|
+
// moves the two files between these homes on every `reconcileWorkspace` run.
|
|
17
|
+
const SUB_AGENT_MIGRATION_FILES = [
|
|
18
|
+
'subagent-progress.json',
|
|
19
|
+
'progress-spawn.json'
|
|
20
|
+
];
|
|
21
|
+
const SUB_AGENTS_DIR = '_sub_agents';
|
|
22
|
+
// As of slice 2026-06-05-peaks-runtime-layer these old paths are the
|
|
23
|
+
// back-compat read-only fallbacks; the canonical new home is
|
|
24
|
+
// `.peaks/_runtime/`. `migrateOldRuntimeState` moves them to the new
|
|
25
|
+
// location on disk. The leading dot is dropped when computing the
|
|
26
|
+
// new basename (e.g. `.session.json` → `session.json`), so the new
|
|
27
|
+
// layout is `.peaks/_runtime/{session.json,active-skill.json,sop-state/}`.
|
|
28
|
+
const RUNTIME_OLD_PATHS = [
|
|
29
|
+
'.session.json',
|
|
30
|
+
'.active-skill.json',
|
|
31
|
+
'sop-state'
|
|
32
|
+
];
|
|
33
|
+
const RUNTIME_DIR = join('.peaks', '_runtime');
|
|
34
|
+
/**
|
|
35
|
+
* Map a legacy path basename (e.g. `.session.json`) to its canonical
|
|
36
|
+
* new basename (e.g. `session.json`). The dot is dropped so the new
|
|
37
|
+
* layer reads naturally. Directories pass through unchanged.
|
|
38
|
+
*/
|
|
39
|
+
function runtimeNewBasename(oldBasename) {
|
|
40
|
+
if (oldBasename.startsWith('.') && oldBasename.length > 1) {
|
|
41
|
+
return oldBasename.slice(1);
|
|
42
|
+
}
|
|
43
|
+
return oldBasename;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* One-time migration step (added in slice 2026-06-05-peaks-runtime-layer).
|
|
47
|
+
*
|
|
48
|
+
* Move the legacy runtime files at:
|
|
49
|
+
* - `.peaks/.session.json`
|
|
50
|
+
* - `.peaks/.active-skill.json`
|
|
51
|
+
* - `.peaks/sop-state/`
|
|
52
|
+
* into their new canonical home at:
|
|
53
|
+
* - `.peaks/_runtime/session.json`
|
|
54
|
+
* - `.peaks/_runtime/active-skill.json`
|
|
55
|
+
* - `.peaks/_runtime/sop-state/`
|
|
56
|
+
*
|
|
57
|
+
* Behavior:
|
|
58
|
+
* - Idempotent: re-running on a tree that is already on the new
|
|
59
|
+
* layout produces `migratedFiles: []`.
|
|
60
|
+
* - Best-effort: uses `fs.renameSync` (atomic on POSIX, best-effort
|
|
61
|
+
* on Windows) and falls back to `copyFileSync` + `unlinkSync` if
|
|
62
|
+
* rename throws (e.g. cross-device move on Windows). Errors are
|
|
63
|
+
* collected per file and returned in the `errors` array so the
|
|
64
|
+
* reconcile envelope can surface them without blocking the rest of
|
|
65
|
+
* the migration.
|
|
66
|
+
* - Creates `.peaks/_runtime/` on demand if any of the old paths
|
|
67
|
+
* are present.
|
|
68
|
+
*
|
|
69
|
+
* @returns `{ migratedFiles, errors }`. `migratedFiles` lists the
|
|
70
|
+
* *old* relative paths (e.g. `.peaks/.session.json`) that were
|
|
71
|
+
* successfully moved, in move order. `errors` lists per-file
|
|
72
|
+
* failures with the old path and a human-readable message.
|
|
73
|
+
*/
|
|
74
|
+
export function migrateOldRuntimeState(projectRoot) {
|
|
75
|
+
const root = resolve(projectRoot);
|
|
76
|
+
const peaksRoot = join(root, '.peaks');
|
|
77
|
+
const newDir = join(root, RUNTIME_DIR);
|
|
78
|
+
const migratedFiles = [];
|
|
79
|
+
const errors = [];
|
|
80
|
+
for (const rel of RUNTIME_OLD_PATHS) {
|
|
81
|
+
const oldPath = join(peaksRoot, rel);
|
|
82
|
+
if (!existsSync(oldPath))
|
|
83
|
+
continue;
|
|
84
|
+
// Skip if the corresponding new path already exists — we treat the
|
|
85
|
+
// new path as authoritative when both exist, so the old file would
|
|
86
|
+
// only be stale data.
|
|
87
|
+
const newPath = join(newDir, runtimeNewBasename(rel));
|
|
88
|
+
if (existsSync(newPath)) {
|
|
89
|
+
// Best-effort cleanup of the stale old file so a re-run stays
|
|
90
|
+
// idempotent and the tree converges on the new layout.
|
|
91
|
+
try {
|
|
92
|
+
rmSync(oldPath, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
errors.push({
|
|
96
|
+
path: rel,
|
|
97
|
+
message: `Could not remove stale legacy file after migration: ${error instanceof Error ? error.message : String(error)}`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
// Ensure the new parent dir exists. `mkdirSync(dirname(newPath), { recursive: true })`
|
|
104
|
+
// covers both the file case (`.peaks/_runtime`) and the
|
|
105
|
+
// directory case (`.peaks/_runtime/sop-state`).
|
|
106
|
+
mkdirSync(dirname(newPath), { recursive: true });
|
|
107
|
+
try {
|
|
108
|
+
renameSync(oldPath, newPath);
|
|
109
|
+
}
|
|
110
|
+
catch (renameError) {
|
|
111
|
+
// Cross-device or locked-file fallback: copy + unlink.
|
|
112
|
+
const stat = lstatSync(oldPath);
|
|
113
|
+
if (stat.isDirectory()) {
|
|
114
|
+
// Recursive copy for the sop-state dir.
|
|
115
|
+
copyDirRecursiveSync(oldPath, newPath);
|
|
116
|
+
rmSync(oldPath, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
copyFileSync(oldPath, newPath);
|
|
120
|
+
unlinkSync(oldPath);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
migratedFiles.push(join('.peaks', rel));
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
errors.push({
|
|
127
|
+
path: rel,
|
|
128
|
+
message: error instanceof Error ? error.message : String(error)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { migratedFiles, errors };
|
|
133
|
+
}
|
|
134
|
+
function copyDirRecursiveSync(src, dest) {
|
|
135
|
+
mkdirSync(dest, { recursive: true });
|
|
136
|
+
for (const name of readdirSync(src)) {
|
|
137
|
+
const childSrc = join(src, name);
|
|
138
|
+
const childDest = join(dest, name);
|
|
139
|
+
const stat = lstatSync(childSrc);
|
|
140
|
+
if (stat.isDirectory()) {
|
|
141
|
+
copyDirRecursiveSync(childSrc, childDest);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
copyFileSync(childSrc, childDest);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* One-time sub-agent state migration (slice 2026-06-06-sub-agent-spawn-bug-and-decouple).
|
|
150
|
+
*
|
|
151
|
+
* Move the legacy per-session sub-agent state files at:
|
|
152
|
+
* - `.peaks/_runtime/<sid>/system/subagent-progress.json`
|
|
153
|
+
* - `.peaks/_runtime/<sid>/system/progress-spawn.json`
|
|
154
|
+
* into the new canonical home at:
|
|
155
|
+
* - `.peaks/_sub_agents/<sid>/subagent-progress.json`
|
|
156
|
+
* - `.peaks/_sub_agents/<sid>/progress-spawn.json`
|
|
157
|
+
*
|
|
158
|
+
* Behavior:
|
|
159
|
+
* - Idempotent: re-running on a tree that is already on the new layout
|
|
160
|
+
* produces `migratedFiles: []`.
|
|
161
|
+
* - Best-effort: uses `fs.renameSync` and falls back to `copyFileSync +
|
|
162
|
+
* unlinkSync` if rename throws (e.g. cross-device move on Windows).
|
|
163
|
+
* - Empty `<sid>/system/` dir removal (R-2 guard): the legacy `system/`
|
|
164
|
+
* subdir is only removed when it has zero other files, so a tree where
|
|
165
|
+
* the user had unrelated content in `system/` is left untouched.
|
|
166
|
+
* - New-path-wins: when both old and new files exist, the old file is
|
|
167
|
+
* removed (the new path is authoritative).
|
|
168
|
+
*
|
|
169
|
+
* Walks every discovered session — not just the canonical one — so a user
|
|
170
|
+
* with 6 pre-migration sessions gets all of them migrated in one reconcile
|
|
171
|
+
* pass.
|
|
172
|
+
*
|
|
173
|
+
* @returns `{ migratedFiles, errors }`. `migratedFiles` lists the *old*
|
|
174
|
+
* relative paths (e.g. `.peaks/_runtime/<sid>/system/subagent-progress.json`) that
|
|
175
|
+
* were successfully moved. `errors` lists per-file failures.
|
|
176
|
+
*/
|
|
177
|
+
export function migrateSubAgentState(projectRoot) {
|
|
178
|
+
const root = resolve(projectRoot);
|
|
179
|
+
const newDir = join(root, '.peaks', SUB_AGENTS_DIR);
|
|
180
|
+
const migratedFiles = [];
|
|
181
|
+
const errors = [];
|
|
182
|
+
for (const session of discoverSessions(projectRoot)) {
|
|
183
|
+
const oldSystemDir = join(session.path, 'system');
|
|
184
|
+
if (!existsSync(oldSystemDir))
|
|
185
|
+
continue;
|
|
186
|
+
const newSessionDir = join(newDir, session.sessionId);
|
|
187
|
+
mkdirSync(newSessionDir, { recursive: true });
|
|
188
|
+
for (const fname of SUB_AGENT_MIGRATION_FILES) {
|
|
189
|
+
const oldPath = join(oldSystemDir, fname);
|
|
190
|
+
const newPath = join(newSessionDir, fname);
|
|
191
|
+
if (!existsSync(oldPath))
|
|
192
|
+
continue;
|
|
193
|
+
if (existsSync(newPath)) {
|
|
194
|
+
// New path is authoritative; remove stale old file.
|
|
195
|
+
try {
|
|
196
|
+
rmSync(oldPath, { force: true });
|
|
197
|
+
}
|
|
198
|
+
catch { /* best effort */ } // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
try {
|
|
203
|
+
renameSync(oldPath, newPath);
|
|
204
|
+
}
|
|
205
|
+
catch (renameError) {
|
|
206
|
+
// Cross-device or locked-file fallback: copy + unlink.
|
|
207
|
+
copyFileSync(oldPath, newPath);
|
|
208
|
+
unlinkSync(oldPath);
|
|
209
|
+
}
|
|
210
|
+
migratedFiles.push(join('.peaks', session.sessionId, 'system', fname));
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
errors.push({
|
|
214
|
+
path: oldPath,
|
|
215
|
+
message: error instanceof Error ? error.message : String(error)
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// R-2 guard: only remove the legacy system/ dir when it has zero
|
|
220
|
+
// remaining files (the user might have unrelated content there).
|
|
221
|
+
try {
|
|
222
|
+
const remaining = readdirSync(oldSystemDir);
|
|
223
|
+
if (remaining.length === 0) {
|
|
224
|
+
rmdirSync(oldSystemDir);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch { /* best effort */ } // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
228
|
+
}
|
|
229
|
+
return { migratedFiles, errors };
|
|
230
|
+
}
|