peaks-loop 4.0.16 → 4.0.17
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 +41 -0
- package/README-en.md +12 -9
- package/README.md +15 -4
- package/config/eslint/.peaks-rules.cjs +39 -26
- package/dist/cli/commands/_register.js +2 -1
- package/dist/cli/commands/audit-commands.d.ts +1 -1
- package/dist/cli/commands/code-gate-command.d.ts +25 -0
- package/dist/cli/commands/code-gate-command.js +86 -0
- package/dist/cli/commands/code-mode-gate-commands.js +3 -4
- package/dist/cli/commands/code-orchestrator-can-do.js +2 -1
- package/dist/cli/commands/cron-commands.js +16 -4
- package/dist/cli/commands/dashboard-long-run.js +8 -4
- package/dist/cli/commands/dispatch-from-dag.d.ts +1 -1
- package/dist/cli/commands/hooks-commands.js +56 -3
- package/dist/cli/commands/qa-commands.js +1 -2
- package/dist/cli/commands/sediment-commands.d.ts +1 -1
- package/dist/cli/commands/slice-commands.js +20 -8
- package/dist/cli/commands/statusline-commands.d.ts +1 -0
- package/dist/cli/commands/statusline-commands.js +17 -3
- package/dist/cli/commands/tech-commands.d.ts +1 -1
- package/dist/services/artifacts/request-artifact-service.d.ts +2 -2
- package/dist/services/artifacts/request-artifact-service.js +10 -1
- package/dist/services/audit/enforcers/lint-reference-shape.js +18 -4
- package/dist/services/code/auto-compact-orchestrator.js +9 -1
- package/dist/services/code/batch-heartbeat-poller.js +26 -0
- package/dist/services/code/orchestrator-can-do.d.ts +24 -0
- package/dist/services/code/orchestrator-can-do.js +48 -2
- package/dist/services/code/post-compact-detector.d.ts +1 -1
- package/dist/services/compact-statusline/compact-statusline-service.js +19 -11
- package/dist/services/config/config-service.d.ts +1 -1
- package/dist/services/config/config-service.js +2 -4
- package/dist/services/container/container-lease.js +21 -8
- package/dist/services/context/spillover-store.d.ts +1 -1
- package/dist/services/crystallization/crystallization-types.js +20 -8
- package/dist/services/crystallization/evidence-brief-builder.js +22 -11
- package/dist/services/dispatch/dispatch-record-writer.js +231 -141
- package/dist/services/evolution/evolution-types.js +38 -21
- package/dist/services/feedback/feedback-promotion-service.js +9 -1
- package/dist/services/hooks/pre-tool-code-gate.d.ts +46 -0
- package/dist/services/hooks/pre-tool-code-gate.js +91 -0
- package/dist/services/ide/current-model-detector.js +1 -2
- package/dist/services/lint/detect-eslint.js +8 -5
- package/dist/services/lint/eslint-runner.d.ts +1 -2
- package/dist/services/lint/eslint-runner.js +86 -23
- package/dist/services/lint/npx-resolver.d.ts +6 -0
- package/dist/services/lint/npx-resolver.js +47 -0
- package/dist/services/loop/loop-release-types.js +32 -13
- package/dist/services/loop/spec-service.d.ts +7 -1
- package/dist/services/loop/spec-service.js +218 -182
- package/dist/services/rd/rd-service.js +22 -9
- package/dist/services/scan/archetype-service.js +21 -9
- package/dist/services/session/binding-store.js +1 -2
- package/dist/services/share/bundle-reader.d.ts +6 -9
- package/dist/services/share/bundle-reader.js +297 -223
- package/dist/services/skills/hooks-settings-service.d.ts +33 -0
- package/dist/services/skills/hooks-settings-service.js +27 -1
- package/dist/services/slice/calibration-store.js +21 -8
- package/dist/services/slice/slice-check-service.js +35 -14
- package/dist/services/slice/slice-decompose-service.js +459 -223
- package/dist/services/standards/project-context.js +135 -73
- package/dist/services/verdict/envelopes.d.ts +1 -2
- package/dist/services/verdict/envelopes.js +1 -2
- package/dist/services/verdict/verdict-aggregator.d.ts +1 -2
- package/dist/services/vm/vm-lease.js +19 -8
- package/dist/services/worktree/worktree-lease.d.ts +0 -10
- package/dist/services/worktree/worktree-lease.js +19 -8
- package/dist/shared/fs-utils.d.ts +1 -1
- package/package.json +7 -4
- package/skills/peaks-code/SKILL.md +46 -0
|
@@ -54,46 +54,83 @@ function majorOf(version) {
|
|
|
54
54
|
const major = cleaned.split('.')[0];
|
|
55
55
|
return major !== undefined && /^\d+$/.test(major) ? major : undefined;
|
|
56
56
|
}
|
|
57
|
+
const LIBRARY_DETECTORS = [
|
|
58
|
+
detectAntd,
|
|
59
|
+
detectMui,
|
|
60
|
+
detectElementPlus,
|
|
61
|
+
detectElementUi,
|
|
62
|
+
detectArco,
|
|
63
|
+
detectTdesign,
|
|
64
|
+
detectSemi,
|
|
65
|
+
detectNextui,
|
|
66
|
+
detectChakra,
|
|
67
|
+
detectVant,
|
|
68
|
+
detectShadcn
|
|
69
|
+
];
|
|
57
70
|
export function detectComponentLibrary(projectRoot, deps) {
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
? { name: 'antd-pro', ...(major !== undefined ? { majorVersion: major } : {}), hasProSuite: true }
|
|
63
|
-
: { name: 'antd', ...(major !== undefined ? { majorVersion: major } : {}) };
|
|
71
|
+
for (const detector of LIBRARY_DETECTORS) {
|
|
72
|
+
const result = detector(deps, projectRoot);
|
|
73
|
+
if (result !== null)
|
|
74
|
+
return result;
|
|
64
75
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
return { name: 'none' };
|
|
77
|
+
}
|
|
78
|
+
function detectAntd(deps, _projectRoot) {
|
|
79
|
+
if (!('antd' in deps))
|
|
80
|
+
return null;
|
|
81
|
+
const major = majorOf(deps['antd']);
|
|
82
|
+
const hasProSuite = '@ant-design/pro-components' in deps
|
|
83
|
+
|| Object.keys(deps).some((d) => d.startsWith('@ant-design/pro-'));
|
|
84
|
+
return hasProSuite
|
|
85
|
+
? { name: 'antd-pro', ...(major !== undefined ? { majorVersion: major } : {}), hasProSuite: true }
|
|
86
|
+
: { name: 'antd', ...(major !== undefined ? { majorVersion: major } : {}) };
|
|
87
|
+
}
|
|
88
|
+
function detectMui(deps, _projectRoot) {
|
|
89
|
+
if (!('@mui/material' in deps))
|
|
90
|
+
return null;
|
|
91
|
+
const major = majorOf(deps['@mui/material']);
|
|
92
|
+
return { name: 'mui', ...(major !== undefined ? { majorVersion: major } : {}) };
|
|
93
|
+
}
|
|
94
|
+
function detectElementPlus(deps, _projectRoot) {
|
|
95
|
+
return 'element-plus' in deps ? { name: 'element-plus' } : null;
|
|
96
|
+
}
|
|
97
|
+
function detectElementUi(deps, _projectRoot) {
|
|
98
|
+
return 'element-ui' in deps ? { name: 'element-ui' } : null;
|
|
99
|
+
}
|
|
100
|
+
function detectArco(deps, _projectRoot) {
|
|
101
|
+
return '@arco-design/web-react' in deps ? { name: 'arco' } : null;
|
|
102
|
+
}
|
|
103
|
+
function detectTdesign(deps, _projectRoot) {
|
|
104
|
+
return ('tdesign-react' in deps || 'tdesign-vue-next' in deps) ? { name: 'tdesign' } : null;
|
|
105
|
+
}
|
|
106
|
+
function detectSemi(deps, _projectRoot) {
|
|
107
|
+
return '@douyinfe/semi-ui' in deps ? { name: 'semi' } : null;
|
|
108
|
+
}
|
|
109
|
+
function detectNextui(deps, _projectRoot) {
|
|
110
|
+
return '@nextui-org/react' in deps ? { name: 'nextui' } : null;
|
|
111
|
+
}
|
|
112
|
+
function detectChakra(deps, _projectRoot) {
|
|
113
|
+
return '@chakra-ui/react' in deps ? { name: 'chakra' } : null;
|
|
114
|
+
}
|
|
115
|
+
function detectVant(deps, _projectRoot) {
|
|
116
|
+
return 'vant' in deps ? { name: 'vant' } : null;
|
|
117
|
+
}
|
|
118
|
+
/** shadcn / ui is a Tailwind-based component primitive set shipped as
|
|
119
|
+
* copyable source files under `components/ui/`. It has no top-level
|
|
120
|
+
* package of its own, so the signal we use is: at least one of its
|
|
121
|
+
* peer utilities (cva, clsx, tailwind-merge, lucide-react) is present
|
|
122
|
+
* AND Tailwind is present (either as a dep or via a components/ui dir). */
|
|
123
|
+
function detectShadcn(deps, projectRoot) {
|
|
124
|
+
const hasPeerUtility = 'class-variance-authority' in deps
|
|
88
125
|
|| 'clsx' in deps
|
|
89
126
|
|| 'tailwind-merge' in deps
|
|
90
|
-
|| 'lucide-react' in deps
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return { name: '
|
|
127
|
+
|| 'lucide-react' in deps;
|
|
128
|
+
if (!hasPeerUtility)
|
|
129
|
+
return null;
|
|
130
|
+
const hasTailwind = 'tailwindcss' in deps
|
|
131
|
+
|| existsSync(join(projectRoot, 'components', 'ui'))
|
|
132
|
+
|| existsSync(join(projectRoot, 'src', 'components', 'ui'));
|
|
133
|
+
return hasTailwind ? { name: 'shadcn' } : null;
|
|
97
134
|
}
|
|
98
135
|
function detectCssFrameworks(projectRoot, deps) {
|
|
99
136
|
const frameworks = [];
|
|
@@ -166,6 +203,11 @@ function detectDataFetching(deps) {
|
|
|
166
203
|
}
|
|
167
204
|
function detectLegacySignals(projectRoot, deps) {
|
|
168
205
|
const signals = [];
|
|
206
|
+
collectPackageLegacySignals(deps, signals);
|
|
207
|
+
collectSourceFileLegacySignals(projectRoot, signals);
|
|
208
|
+
return signals;
|
|
209
|
+
}
|
|
210
|
+
function collectPackageLegacySignals(deps, signals) {
|
|
169
211
|
if ('moment' in deps)
|
|
170
212
|
signals.push('`moment` in deps — prefer `dayjs` or `date-fns` for new code');
|
|
171
213
|
if (Object.keys(deps).some((d) => d.startsWith('enzyme')))
|
|
@@ -180,59 +222,53 @@ function detectLegacySignals(projectRoot, deps) {
|
|
|
180
222
|
signals.push('Backbone — legacy; do not add new Backbone code');
|
|
181
223
|
if (deps['vue']?.startsWith('2') === true)
|
|
182
224
|
signals.push('Vue 2 — preserve Options API for existing components');
|
|
183
|
-
|
|
225
|
+
}
|
|
226
|
+
function collectSourceFileLegacySignals(projectRoot, signals) {
|
|
184
227
|
const srcRoot = join(projectRoot, 'src');
|
|
185
|
-
if (existsSync(srcRoot))
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
228
|
+
if (!existsSync(srcRoot))
|
|
229
|
+
return;
|
|
230
|
+
const sample = sampleSourceFiles(srcRoot, 80);
|
|
231
|
+
const { classComponentHits, inlineStyleHits } = scanSampleForLegacyMarkers(sample);
|
|
232
|
+
if (classComponentHits >= 1)
|
|
233
|
+
signals.push(`React class components detected (${classComponentHits}+ files) — keep class style for existing modules, use function components + hooks for new code`);
|
|
234
|
+
if (inlineStyleHits >= 50)
|
|
235
|
+
signals.push(`Inline styles dominant (${inlineStyleHits}+ occurrences) — match existing styling for new code in same modules`);
|
|
236
|
+
}
|
|
237
|
+
function scanSampleForLegacyMarkers(sample) {
|
|
238
|
+
let classComponentHits = 0;
|
|
239
|
+
let inlineStyleHits = 0;
|
|
240
|
+
for (const filePath of sample) {
|
|
241
|
+
try {
|
|
242
|
+
const content = readFileSync(filePath, 'utf8');
|
|
243
|
+
if (/extends\s+(?:React\.)?Component\b/.test(content))
|
|
244
|
+
classComponentHits += 1;
|
|
245
|
+
const matches = content.match(/style=\{\{/g);
|
|
246
|
+
if (matches !== null)
|
|
247
|
+
inlineStyleHits += matches.length;
|
|
248
|
+
}
|
|
249
|
+
catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
250
|
+
// ignore unreadable files
|
|
201
251
|
}
|
|
202
|
-
if (classComponentHits >= 1)
|
|
203
|
-
signals.push(`React class components detected (${classComponentHits}+ files) — keep class style for existing modules, use function components + hooks for new code`);
|
|
204
|
-
if (inlineStyleHits >= 50)
|
|
205
|
-
signals.push(`Inline styles dominant (${inlineStyleHits}+ occurrences) — match existing styling for new code in same modules`);
|
|
206
252
|
}
|
|
207
|
-
return
|
|
253
|
+
return { classComponentHits, inlineStyleHits };
|
|
208
254
|
}
|
|
209
255
|
function sampleSourceFiles(root, limit) {
|
|
210
256
|
const out = [];
|
|
211
257
|
const queue = [root];
|
|
212
258
|
while (queue.length > 0 && out.length < limit) {
|
|
213
259
|
const dir = queue.shift();
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
entries = readdirSync(dir);
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
260
|
+
const entries = readDirOrEmpty(dir);
|
|
221
261
|
for (const entry of entries) {
|
|
222
|
-
if (
|
|
262
|
+
if (isIgnoredEntry(entry))
|
|
223
263
|
continue;
|
|
224
264
|
const full = join(dir, entry);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
s = statSync(full);
|
|
228
|
-
}
|
|
229
|
-
catch {
|
|
265
|
+
const s = statOrNull(full);
|
|
266
|
+
if (s === null || s === undefined)
|
|
230
267
|
continue;
|
|
231
|
-
}
|
|
232
268
|
if (s.isDirectory()) {
|
|
233
269
|
queue.push(full);
|
|
234
270
|
}
|
|
235
|
-
else if (
|
|
271
|
+
else if (isJsxFile(entry)) {
|
|
236
272
|
out.push(full);
|
|
237
273
|
if (out.length >= limit)
|
|
238
274
|
break;
|
|
@@ -241,6 +277,32 @@ function sampleSourceFiles(root, limit) {
|
|
|
241
277
|
}
|
|
242
278
|
return out;
|
|
243
279
|
}
|
|
280
|
+
function readDirOrEmpty(dir) {
|
|
281
|
+
try {
|
|
282
|
+
return readdirSync(dir);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function statOrNull(path) {
|
|
289
|
+
try {
|
|
290
|
+
return statSync(path);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function isIgnoredEntry(entry) {
|
|
297
|
+
if (entry.startsWith('.'))
|
|
298
|
+
return true;
|
|
299
|
+
if (entry === 'node_modules' || entry === 'dist' || entry === 'build')
|
|
300
|
+
return true;
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
function isJsxFile(entry) {
|
|
304
|
+
return /\.(tsx|jsx)$/.test(entry);
|
|
305
|
+
}
|
|
244
306
|
function notableDepsList(deps) {
|
|
245
307
|
const interesting = ['monaco-editor', '@monaco-editor/react', 'react-querybuilder', '@dnd-kit/core', 'react-dnd', 'echarts', 'recharts', '@ant-design/charts', 'antd-style', 'lodash', 'lodash-es', 'rxjs', 'socket.io-client'];
|
|
246
308
|
return interesting.filter((d) => d in deps);
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
* "missing → null" contract). Callers can detect "envelope missing"
|
|
24
24
|
* without throwing.
|
|
25
25
|
*/
|
|
26
|
-
import { type SecurityAuditEnvelope } from '../audit-independent/index.js';
|
|
27
|
-
import { type PerfAuditEnvelope } from '../audit-independent/index.js';
|
|
26
|
+
import { type PerfAuditEnvelope, type SecurityAuditEnvelope } from '../audit-independent/index.js';
|
|
28
27
|
import { type KarpathyEnvelope, type MutEnvelope, type QaEnvelope, type AggregatorInput } from './verdict-aggregator.js';
|
|
29
28
|
/**
|
|
30
29
|
* `kind` is the discriminator. The on-disk file format is unchanged;
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
* "missing → null" contract). Callers can detect "envelope missing"
|
|
24
24
|
* without throwing.
|
|
25
25
|
*/
|
|
26
|
-
import { isSecurityAuditEnvelope } from '../audit-independent/index.js';
|
|
27
|
-
import { isPerfAuditEnvelope } from '../audit-independent/index.js';
|
|
26
|
+
import { isPerfAuditEnvelope, isSecurityAuditEnvelope } from '../audit-independent/index.js';
|
|
28
27
|
// ─── 5 parsers (pure, never throw) ─────────────────────────────────────
|
|
29
28
|
/**
|
|
30
29
|
* v2.13.3 AC-1 — parse real v2.12.0 audit markdown.
|
|
@@ -27,8 +27,7 @@
|
|
|
27
27
|
* SecurityAuditEnvelope / PerfAuditEnvelope type re-exports.
|
|
28
28
|
* - hard precedence; no scoring, no weighting, no RFC voting.
|
|
29
29
|
*/
|
|
30
|
-
import type { SecurityAuditEnvelope } from '../audit-independent/index.js';
|
|
31
|
-
import type { PerfAuditEnvelope } from '../audit-independent/index.js';
|
|
30
|
+
import type { PerfAuditEnvelope, SecurityAuditEnvelope } from '../audit-independent/index.js';
|
|
32
31
|
import type { ThresholdViolation } from 'peaks-loop-mut';
|
|
33
32
|
export interface KarpathyViolation {
|
|
34
33
|
readonly guideline: 'think-before-coding' | 'simplicity-first' | 'surgical-changes' | 'goal-driven-execution';
|
|
@@ -24,13 +24,24 @@ import { normalizePath } from '../../shared/path-utils.js';
|
|
|
24
24
|
export const VM_HYPERVISORS = Object.freeze([
|
|
25
25
|
'kvm', 'hyperkit', 'hyperv'
|
|
26
26
|
]);
|
|
27
|
+
// PRD-002b slice 2 — extract lease-TTL primitives (same shape as
|
|
28
|
+
// container-lease.ts / worktree-lease.ts). See config diff comment.
|
|
29
|
+
const MINUTES_PER_HOUR = 60;
|
|
30
|
+
const MS_PER_MINUTE = MINUTES_PER_HOUR * 1_000;
|
|
31
|
+
const LEASE_RD_TTL_MINUTES = 30;
|
|
32
|
+
const LEASE_QA_TTL_MINUTES = 15;
|
|
33
|
+
const LEASE_UI_TTL_MINUTES = MINUTES_PER_HOUR;
|
|
34
|
+
const LEASE_SC_TTL_MINUTES = 30;
|
|
35
|
+
const LEASE_PRD_TTL_MINUTES = 15;
|
|
36
|
+
const LEASE_GENERAL_TTL_MINUTES = 30;
|
|
37
|
+
const LEASE_ID_RANDOM_BYTES = 8;
|
|
27
38
|
export const DEFAULT_TTL_BY_ROLE = Object.freeze({
|
|
28
|
-
rd:
|
|
29
|
-
qa:
|
|
30
|
-
ui:
|
|
31
|
-
sc:
|
|
32
|
-
prd:
|
|
33
|
-
general:
|
|
39
|
+
rd: LEASE_RD_TTL_MINUTES * MS_PER_MINUTE,
|
|
40
|
+
qa: LEASE_QA_TTL_MINUTES * MS_PER_MINUTE,
|
|
41
|
+
ui: LEASE_UI_TTL_MINUTES * MS_PER_MINUTE,
|
|
42
|
+
sc: LEASE_SC_TTL_MINUTES * MS_PER_MINUTE,
|
|
43
|
+
prd: LEASE_PRD_TTL_MINUTES * MS_PER_MINUTE,
|
|
44
|
+
general: LEASE_GENERAL_TTL_MINUTES * MS_PER_MINUTE
|
|
34
45
|
});
|
|
35
46
|
export const DEFAULT_TTL_MS = DEFAULT_TTL_BY_ROLE.rd;
|
|
36
47
|
export const DEFAULT_VM_IMAGE = 'peaks-base:22-slim';
|
|
@@ -41,12 +52,12 @@ export function vmLeaseFilePath(sessionRuntimeDir, leaseId) {
|
|
|
41
52
|
return joinPath(vmLeaseStoreDir(sessionRuntimeDir), `${leaseId}.json`);
|
|
42
53
|
}
|
|
43
54
|
export function generateVmLeaseId() {
|
|
44
|
-
return randomBytes(
|
|
55
|
+
return randomBytes(LEASE_ID_RANDOM_BYTES).toString('hex');
|
|
45
56
|
}
|
|
46
57
|
export function ttlForVmRole(role) {
|
|
47
58
|
const normalized = role.toLowerCase();
|
|
48
59
|
const candidate = DEFAULT_TTL_BY_ROLE[normalized];
|
|
49
|
-
const fallback = DEFAULT_TTL_BY_ROLE['rd'] ??
|
|
60
|
+
const fallback = DEFAULT_TTL_BY_ROLE['rd'] ?? LEASE_RD_TTL_MINUTES * MS_PER_MINUTE;
|
|
50
61
|
return candidate ?? fallback;
|
|
51
62
|
}
|
|
52
63
|
export function finalizeVmLease(draft) {
|
|
@@ -24,16 +24,6 @@
|
|
|
24
24
|
* implicitly authorizes worktree operations on its path (the hook
|
|
25
25
|
* integration is Part 2 of this slice).
|
|
26
26
|
*/
|
|
27
|
-
/**
|
|
28
|
-
* Per-role default TTL. Sub-agent dispatch duration varies by role:
|
|
29
|
-
* - rd (long planning + impl) → 30 min
|
|
30
|
-
* - qa (test design + browser validation) → 15 min
|
|
31
|
-
* - ui (high-fidelity prototype iteration) → 1 h
|
|
32
|
-
* - txt (handoff capsule, no worktree expected) → n/a (no worktree)
|
|
33
|
-
* - general-purpose / unknown → 30 min (rd-like)
|
|
34
|
-
*
|
|
35
|
-
* Users MAY override with `--ttl <duration>` (e.g. `--ttl 4h`).
|
|
36
|
-
*/
|
|
37
27
|
export declare const DEFAULT_TTL_BY_ROLE: Readonly<Record<string, number>>;
|
|
38
28
|
export declare const DEFAULT_TTL_MS: number | undefined;
|
|
39
29
|
export type WorktreeLeaseStatus = 'active' | 'released' | 'expired' | 'gc';
|
|
@@ -37,13 +37,24 @@ import { normalizePath } from '../../shared/path-utils.js';
|
|
|
37
37
|
*
|
|
38
38
|
* Users MAY override with `--ttl <duration>` (e.g. `--ttl 4h`).
|
|
39
39
|
*/
|
|
40
|
+
// PRD-002b slice 2 — extract lease-TTL primitives (same shape as
|
|
41
|
+
// container-lease.ts / vm-lease.ts). See config diff comment.
|
|
42
|
+
const MINUTES_PER_HOUR = 60;
|
|
43
|
+
const MS_PER_MINUTE = MINUTES_PER_HOUR * 1_000;
|
|
44
|
+
const LEASE_RD_TTL_MINUTES = 30;
|
|
45
|
+
const LEASE_QA_TTL_MINUTES = 15;
|
|
46
|
+
const LEASE_UI_TTL_MINUTES = MINUTES_PER_HOUR;
|
|
47
|
+
const LEASE_SC_TTL_MINUTES = 30;
|
|
48
|
+
const LEASE_PRD_TTL_MINUTES = 15;
|
|
49
|
+
const LEASE_GENERAL_TTL_MINUTES = 30;
|
|
50
|
+
const LEASE_ID_RANDOM_BYTES = 8;
|
|
40
51
|
export const DEFAULT_TTL_BY_ROLE = Object.freeze({
|
|
41
|
-
rd:
|
|
42
|
-
qa:
|
|
43
|
-
ui:
|
|
44
|
-
sc:
|
|
45
|
-
prd:
|
|
46
|
-
general:
|
|
52
|
+
rd: LEASE_RD_TTL_MINUTES * MS_PER_MINUTE,
|
|
53
|
+
qa: LEASE_QA_TTL_MINUTES * MS_PER_MINUTE,
|
|
54
|
+
ui: LEASE_UI_TTL_MINUTES * MS_PER_MINUTE,
|
|
55
|
+
sc: LEASE_SC_TTL_MINUTES * MS_PER_MINUTE,
|
|
56
|
+
prd: LEASE_PRD_TTL_MINUTES * MS_PER_MINUTE,
|
|
57
|
+
general: LEASE_GENERAL_TTL_MINUTES * MS_PER_MINUTE
|
|
47
58
|
});
|
|
48
59
|
export const DEFAULT_TTL_MS = DEFAULT_TTL_BY_ROLE.rd;
|
|
49
60
|
/**
|
|
@@ -76,7 +87,7 @@ export function worktreePath(sessionRuntimeDir, leaseId) {
|
|
|
76
87
|
* are irrelevant because each session has its own lease directory.
|
|
77
88
|
*/
|
|
78
89
|
export function generateLeaseId() {
|
|
79
|
-
return randomBytes(
|
|
90
|
+
return randomBytes(LEASE_ID_RANDOM_BYTES).toString('hex');
|
|
80
91
|
}
|
|
81
92
|
/**
|
|
82
93
|
* Compute the TTL ms for a role, falling back to DEFAULT_TTL_MS for
|
|
@@ -86,7 +97,7 @@ export function generateLeaseId() {
|
|
|
86
97
|
export function ttlForRole(role) {
|
|
87
98
|
const normalized = role.toLowerCase();
|
|
88
99
|
const candidate = DEFAULT_TTL_BY_ROLE[normalized];
|
|
89
|
-
const fallback = DEFAULT_TTL_BY_ROLE['rd'] ??
|
|
100
|
+
const fallback = DEFAULT_TTL_BY_ROLE['rd'] ?? LEASE_RD_TTL_MINUTES * MS_PER_MINUTE;
|
|
90
101
|
return candidate ?? fallback;
|
|
91
102
|
}
|
|
92
103
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Platform } from './platform.js';
|
|
2
2
|
export declare function getDirectoryLinkType(targetPlatform?: Platform): 'junction' | 'dir';
|
|
3
3
|
export declare function createDirectoryLinkSync(target: string, linkPath: string): void;
|
|
4
4
|
export declare function readDirectoryLinkTarget(linkPath: string): string | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peaks-loop",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.17",
|
|
4
4
|
"description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
|
|
5
5
|
"author": "SquabbyZ",
|
|
6
6
|
"keywords": [
|
|
@@ -101,9 +101,9 @@
|
|
|
101
101
|
"headroom-ai": "0.22.4",
|
|
102
102
|
"yaml": "^2.9.0",
|
|
103
103
|
"zod": "^3.25.76",
|
|
104
|
-
"peaks-loop-mut": "0.1.
|
|
105
|
-
"peaks-loop-shared-channel": "0.0.
|
|
106
|
-
"peaks-loop-shared": "0.0.
|
|
104
|
+
"peaks-loop-mut": "0.1.20",
|
|
105
|
+
"peaks-loop-shared-channel": "0.0.24",
|
|
106
|
+
"peaks-loop-shared": "0.0.48"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
109
|
"@changesets/cli": "2.31.1",
|
|
@@ -113,9 +113,12 @@
|
|
|
113
113
|
"@stryker-mutator/vitest-runner": "^9.6.1",
|
|
114
114
|
"@types/better-sqlite3": "^7.6.13",
|
|
115
115
|
"@types/node": "^22.10.2",
|
|
116
|
+
"@typescript-eslint/eslint-plugin": "8.66.0",
|
|
117
|
+
"@typescript-eslint/parser": "8.66.0",
|
|
116
118
|
"@vitest/coverage-istanbul": "^4.1.10",
|
|
117
119
|
"@vitest/coverage-v8": "^4.1.10",
|
|
118
120
|
"c8": "^10.1.3",
|
|
121
|
+
"eslint": "8.57.1",
|
|
119
122
|
"tsx": "^4.19.2",
|
|
120
123
|
"typescript": "^5.7.2",
|
|
121
124
|
"vite": "^6.0.0",
|
|
@@ -44,6 +44,52 @@ This skill is the **primary surface**. The `peaks <cmd>` CLI is **auxiliary**
|
|
|
44
44
|
|
|
45
45
|
**Peaks-Loop Code is an orchestrator, NOT an implementer. You MUST NOT write, edit, or modify any application source code directly.** Every code change goes through `peaks-code → RD → QA → verdict`. **If you catch yourself about to write code, STOP.** Hand off to RD. Before declaring workflow complete, run `peaks workflow verify-pipeline --rid <rid> --project <repo> --json`.
|
|
46
46
|
|
|
47
|
+
### Hard-blocked path families (the orchestrator may NOT directly Edit/Write these)
|
|
48
|
+
|
|
49
|
+
| Family | Examples |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `src/` | `src/services/foo.ts`, `src/cli/commands/x.ts` |
|
|
52
|
+
| `tests/unit/` | `tests/unit/services/foo.test.ts` |
|
|
53
|
+
| `tests/integration/` | `tests/integration/x.test.ts` |
|
|
54
|
+
| `config/` | `config/peaks.json`, `tsconfig.json` |
|
|
55
|
+
| `bin/` | `bin/peaks.js` |
|
|
56
|
+
| `scripts/` | `scripts/release.sh` |
|
|
57
|
+
|
|
58
|
+
ANY direct `Edit` / `Write` / `MultiEdit` of these paths from the orchestrator context is a **hard error**. The orchestrator's role is ONLY to dispatch sub-agents; it never owns a code-change tool call against these families.
|
|
59
|
+
|
|
60
|
+
### Allowed path families (orchestrator may touch freely)
|
|
61
|
+
|
|
62
|
+
- `.peaks/**` (memory sediment, project metadata, audit logs)
|
|
63
|
+
- `.peaks/_runtime/**` (session-bound reviewable artifacts, gitignored)
|
|
64
|
+
- `skills/**/SKILL.md` and other skill manifest files (orchestrator self-documents)
|
|
65
|
+
- `docs/**` (governance / spec / plan docs)
|
|
66
|
+
|
|
67
|
+
These families are non-source-code; the orchestrator may `Edit`/`Write` them directly (memory sediment, SKILL.md prose updates, doc snapshots). The PreToolUse hook + the `orchestrator-can-do` probe do NOT block these.
|
|
68
|
+
|
|
69
|
+
### Code-Gate (slice 2026-08-06-codegate-vendor-neutral)
|
|
70
|
+
|
|
71
|
+
Two-layer enforcement — do NOT try to bypass either:
|
|
72
|
+
|
|
73
|
+
1. **LLM-level guard — `peaks code orchestrator-can-do`** (Step 0.51). Before any Edit/Write/MultiEdit tool call, run:
|
|
74
|
+
```bash
|
|
75
|
+
peaks code orchestrator-can-do --slice-spec '<your task>' --json
|
|
76
|
+
```
|
|
77
|
+
For source-code changes (mentioning `src/`, `tests/unit/`, `tests/integration/`, `config/`, `bin/`, `scripts/`) the probe MUST return `canDoInSession: false` with `blockers: ["requires-sub-agent-dispatch"]`. The probe does NOT block non-source slices (memory sediment, docs, SKILL.md updates).
|
|
78
|
+
|
|
79
|
+
2. **Tool-level guard — `pre-tool-code-gate.sh`** (PreToolUse hook). When the orchestrator reaches for `Edit`/`Write`/`MultiEdit` against a hard-blocked path family, the hook exits 2 with stderr `PEAKS_CODE_PROHIBITED_DIRECT_EDIT: <file>... use 'peaks sub-agent dispatch rd'`. The hook is vendor-neutral — it inspects only the standard `{tool, input}` JSON contract, never env vars.
|
|
80
|
+
|
|
81
|
+
### If you catch yourself reaching for Edit/Write on `src/**`
|
|
82
|
+
|
|
83
|
+
STOP. Run:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
peaks sub-agent dispatch rd --prompt '<your task>' --request-id <rid> --project . --batch-id <uuid> --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The RD sub-agent owns the actual `Edit`/`Write`/`MultiEdit` tool calls against source code. The orchestrator only emits the dispatch request.
|
|
90
|
+
|
|
91
|
+
**Anti-pattern:** directly calling `Edit`/`Write`/`MultiEdit` on `src/**` from the orchestrator session because "the change is tiny" / "it's just one line" / "the dispatch overhead is too high" / "the LLM feels confident". None of these override the Code-Gate; the probe + the hook both fail-closed.
|
|
92
|
+
|
|
47
93
|
## 24h mode (orchestrator flag, NOT a new skill)
|
|
48
94
|
|
|
49
95
|
24h mode is a *flag* the orchestrator flips, not a sibling skill. See `.peaks/memory/2026-07-28-peaks-code-loop-skill-proposal.md` for the RL-8 tension analysis. State machine via `peaks session 24h-mode {state,transition,attempts,reset}`; snapshot at `.peaks/_runtime/<sessionId>/24h-state.json` (read-only for peaks-code).
|