peaks-loop 4.0.15 → 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.
Files changed (90) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README-en.md +12 -9
  3. package/README.md +15 -4
  4. package/config/eslint/.peaks-rules.cjs +47 -27
  5. package/dist/cli/commands/_register.js +4 -2
  6. package/dist/cli/commands/audit-commands.d.ts +1 -1
  7. package/dist/cli/commands/code-gate-command.d.ts +25 -0
  8. package/dist/cli/commands/code-gate-command.js +86 -0
  9. package/dist/cli/commands/code-mode-gate-commands.js +3 -4
  10. package/dist/cli/commands/code-orchestrator-can-do.js +2 -1
  11. package/dist/cli/commands/code-review-commands.d.ts +2 -30
  12. package/dist/cli/commands/code-review-commands.js +43 -65
  13. package/dist/cli/commands/cron-commands.js +16 -4
  14. package/dist/cli/commands/dashboard-long-run.js +8 -4
  15. package/dist/cli/commands/dispatch-from-dag.d.ts +1 -1
  16. package/dist/cli/commands/hooks-commands.js +56 -3
  17. package/dist/cli/commands/lint-commands.d.ts +21 -0
  18. package/dist/cli/commands/lint-commands.js +146 -0
  19. package/dist/cli/commands/qa-commands.js +1 -2
  20. package/dist/cli/commands/security-audit-commands.d.ts +1 -1
  21. package/dist/cli/commands/security-audit-commands.js +1 -1
  22. package/dist/cli/commands/sediment-commands.d.ts +1 -1
  23. package/dist/cli/commands/slice-commands.js +20 -8
  24. package/dist/cli/commands/statusline-commands.d.ts +1 -0
  25. package/dist/cli/commands/statusline-commands.js +17 -3
  26. package/dist/cli/commands/tech-commands.d.ts +1 -1
  27. package/dist/services/artifacts/request-artifact-service.d.ts +2 -2
  28. package/dist/services/artifacts/request-artifact-service.js +10 -1
  29. package/dist/services/audit/enforcers/lint-reference-shape.js +18 -4
  30. package/dist/services/code/auto-compact-orchestrator.js +9 -1
  31. package/dist/services/code/batch-heartbeat-poller.js +26 -0
  32. package/dist/services/code/orchestrator-can-do.d.ts +24 -0
  33. package/dist/services/code/orchestrator-can-do.js +48 -2
  34. package/dist/services/code/post-compact-detector.d.ts +1 -1
  35. package/dist/services/code-review/ecc-bridge.d.ts +2 -7
  36. package/dist/services/compact-statusline/compact-statusline-service.js +19 -11
  37. package/dist/services/config/config-service.d.ts +2 -19
  38. package/dist/services/config/config-service.js +2 -56
  39. package/dist/services/config/config-types.d.ts +4 -24
  40. package/dist/services/config/config-types.js +1 -10
  41. package/dist/services/container/container-lease.js +21 -8
  42. package/dist/services/context/spillover-store.d.ts +1 -1
  43. package/dist/services/crystallization/crystallization-types.js +20 -8
  44. package/dist/services/crystallization/evidence-brief-builder.js +22 -11
  45. package/dist/services/dispatch/dispatch-record-writer.js +231 -141
  46. package/dist/services/evolution/evolution-types.js +38 -21
  47. package/dist/services/feedback/feedback-promotion-service.js +9 -1
  48. package/dist/services/hooks/pre-tool-code-gate.d.ts +46 -0
  49. package/dist/services/hooks/pre-tool-code-gate.js +91 -0
  50. package/dist/services/ide/current-model-detector.js +1 -2
  51. package/dist/services/lint/detect-eslint.d.ts +10 -0
  52. package/dist/services/lint/detect-eslint.js +62 -0
  53. package/dist/services/lint/detect-ocr-18.d.ts +10 -0
  54. package/dist/services/lint/detect-ocr-18.js +40 -0
  55. package/dist/services/lint/eslint-runner.d.ts +57 -0
  56. package/dist/services/lint/eslint-runner.js +370 -0
  57. package/dist/services/lint/npx-resolver.d.ts +6 -0
  58. package/dist/services/lint/npx-resolver.js +47 -0
  59. package/dist/services/lint/ocr-multilang-adapter.d.ts +34 -0
  60. package/dist/services/lint/ocr-multilang-adapter.js +143 -0
  61. package/dist/services/loop/loop-release-types.js +32 -13
  62. package/dist/services/loop/spec-service.d.ts +7 -1
  63. package/dist/services/loop/spec-service.js +218 -182
  64. package/dist/services/rd/rd-service.js +22 -9
  65. package/dist/services/scan/archetype-service.js +21 -9
  66. package/dist/services/session/binding-store.js +1 -2
  67. package/dist/services/share/bundle-reader.d.ts +6 -9
  68. package/dist/services/share/bundle-reader.js +297 -223
  69. package/dist/services/skills/hooks-settings-service.d.ts +33 -0
  70. package/dist/services/skills/hooks-settings-service.js +27 -1
  71. package/dist/services/slice/calibration-store.js +21 -8
  72. package/dist/services/slice/slice-check-service.js +35 -14
  73. package/dist/services/slice/slice-decompose-service.js +459 -223
  74. package/dist/services/standards/project-context.js +135 -73
  75. package/dist/services/verdict/envelopes.d.ts +1 -2
  76. package/dist/services/verdict/envelopes.js +1 -2
  77. package/dist/services/verdict/verdict-aggregator.d.ts +1 -2
  78. package/dist/services/vm/vm-lease.js +19 -8
  79. package/dist/services/worktree/worktree-lease.d.ts +0 -10
  80. package/dist/services/worktree/worktree-lease.js +19 -8
  81. package/dist/shared/fs-utils.d.ts +1 -1
  82. package/package.json +8 -12
  83. package/skills/bee/peaks-rd/SKILL.md +1 -1
  84. package/skills/bee/peaks-rd/references/jsts-eslint-gate.md +197 -0
  85. package/skills/bee/peaks-rd/references/ocr-multilang-1.8.md +132 -0
  86. package/skills/bee/peaks-rd/references/parallel-review-fanout.md +1 -1
  87. package/skills/peaks-code/SKILL.md +47 -1
  88. package/dist/services/code-review/ocr-service.d.ts +0 -129
  89. package/dist/services/code-review/ocr-service.js +0 -372
  90. package/skills/bee/peaks-rd/references/ocr-integration.md +0 -229
@@ -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
- if ('antd' in deps) {
59
- const major = majorOf(deps['antd']);
60
- const hasProSuite = '@ant-design/pro-components' in deps || Object.keys(deps).some((d) => d.startsWith('@ant-design/pro-'));
61
- return hasProSuite
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
- if ('@mui/material' in deps)
66
- return { name: 'mui', ...(majorOf(deps['@mui/material']) !== undefined ? { majorVersion: majorOf(deps['@mui/material']) } : {}) };
67
- if ('element-plus' in deps)
68
- return { name: 'element-plus' };
69
- if ('element-ui' in deps)
70
- return { name: 'element-ui' };
71
- if ('@arco-design/web-react' in deps)
72
- return { name: 'arco' };
73
- if ('tdesign-react' in deps || 'tdesign-vue-next' in deps)
74
- return { name: 'tdesign' };
75
- if ('@douyinfe/semi-ui' in deps)
76
- return { name: 'semi' };
77
- if ('@nextui-org/react' in deps)
78
- return { name: 'nextui' };
79
- if ('@chakra-ui/react' in deps)
80
- return { name: 'chakra' };
81
- if ('vant' in deps)
82
- return { name: 'vant' };
83
- // shadcn / ui is a Tailwind-based component primitive set shipped as
84
- // copyable source files under `components/ui/`. It has no top-level
85
- // package of its own, so the signal we use is: Tailwind is present
86
- // AND at least one of its peer utilities is also present.
87
- if (('class-variance-authority' in deps
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
- && ('tailwindcss' in deps
92
- || existsSync(join(projectRoot, 'components', 'ui'))
93
- || existsSync(join(projectRoot, 'src', 'components', 'ui')))) {
94
- return { name: 'shadcn' };
95
- }
96
- return { name: 'none' };
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
- // Lightweight heuristic for class components and inline styles in src/
225
+ }
226
+ function collectSourceFileLegacySignals(projectRoot, signals) {
184
227
  const srcRoot = join(projectRoot, 'src');
185
- if (existsSync(srcRoot)) {
186
- const sample = sampleSourceFiles(srcRoot, 80);
187
- let classComponentHits = 0;
188
- let inlineStyleHits = 0;
189
- for (const filePath of sample) {
190
- try {
191
- const content = readFileSync(filePath, 'utf8');
192
- if (/extends\s+(?:React\.)?Component\b/.test(content))
193
- classComponentHits += 1;
194
- const matches = content.match(/style=\{\{/g);
195
- if (matches !== null)
196
- inlineStyleHits += matches.length;
197
- }
198
- catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
199
- // ignore unreadable files
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 signals;
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
- let entries;
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 (entry.startsWith('.') || entry === 'node_modules' || entry === 'dist' || entry === 'build')
262
+ if (isIgnoredEntry(entry))
223
263
  continue;
224
264
  const full = join(dir, entry);
225
- let s;
226
- try {
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 (/\.(tsx|jsx)$/.test(entry)) {
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: 30 * 60 * 1_000,
29
- qa: 15 * 60 * 1_000,
30
- ui: 60 * 60 * 1_000,
31
- sc: 30 * 60 * 1_000,
32
- prd: 15 * 60 * 1_000,
33
- general: 30 * 60 * 1_000
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(8).toString('hex');
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'] ?? 30 * 60 * 1_000;
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: 30 * 60 * 1_000,
42
- qa: 15 * 60 * 1_000,
43
- ui: 60 * 60 * 1_000,
44
- sc: 30 * 60 * 1_000,
45
- prd: 15 * 60 * 1_000,
46
- general: 30 * 60 * 1_000
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(8).toString('hex');
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'] ?? 30 * 60 * 1_000;
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 { Platform } from './platform.js';
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.15",
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": [
@@ -93,6 +93,7 @@
93
93
  "node": ">=20.0.0"
94
94
  },
95
95
  "dependencies": {
96
+ "@alibaba-group/open-code-review": "^1.8.9",
96
97
  "@colbymchenry/codegraph": "0.7.10",
97
98
  "better-sqlite3": "^12.11.1",
98
99
  "commander": "^12.1.0",
@@ -100,17 +101,9 @@
100
101
  "headroom-ai": "0.22.4",
101
102
  "yaml": "^2.9.0",
102
103
  "zod": "^3.25.76",
103
- "peaks-loop-mut": "0.1.19",
104
- "peaks-loop-shared-channel": "0.0.23",
105
- "peaks-loop-shared": "0.0.46"
106
- },
107
- "peerDependencies": {
108
- "@alibaba-group/open-code-review": "1.3.1"
109
- },
110
- "peerDependenciesMeta": {
111
- "@alibaba-group/open-code-review": {
112
- "optional": true
113
- }
104
+ "peaks-loop-mut": "0.1.20",
105
+ "peaks-loop-shared-channel": "0.0.24",
106
+ "peaks-loop-shared": "0.0.48"
114
107
  },
115
108
  "devDependencies": {
116
109
  "@changesets/cli": "2.31.1",
@@ -120,9 +113,12 @@
120
113
  "@stryker-mutator/vitest-runner": "^9.6.1",
121
114
  "@types/better-sqlite3": "^7.6.13",
122
115
  "@types/node": "^22.10.2",
116
+ "@typescript-eslint/eslint-plugin": "8.66.0",
117
+ "@typescript-eslint/parser": "8.66.0",
123
118
  "@vitest/coverage-istanbul": "^4.1.10",
124
119
  "@vitest/coverage-v8": "^4.1.10",
125
120
  "c8": "^10.1.3",
121
+ "eslint": "8.57.1",
126
122
  "tsx": "^4.19.2",
127
123
  "typescript": "^5.7.2",
128
124
  "vite": "^6.0.0",
@@ -84,7 +84,7 @@ See `references/rd-runbook.md` for the full 9-step runbook (steps #0–#8) with
84
84
 
85
85
  You cannot declare a phase complete from memory. Each gate below is a `ls` or `grep` command you **MUST run** and whose output you **MUST see** before proceeding. CLI enforcement: the gates are ALSO enforced by `peaks request transition`, which fails with `code: PREREQUISITES_MISSING` if any are absent.
86
86
 
87
- Gate index: A (project-scan), A2 (tech-doc path verification), A3 (CLAUDE.md + .claude/rules), B (tech-doc + request artifact), B2 (unit tests on changed surface), B3 (code-review.md), B4 (security-review.md), B5 (lint — no unfilled placeholders), B6 (request-type-sanity), B7 (repair-status — 3-cycle cap), B8 (diff-vs-scope), B9 (perf-baseline).
87
+ Gate index: A (project-scan), A2 (tech-doc path verification), A3 (CLAUDE.md + .claude/rules), B (tech-doc + request artifact), B2 (unit tests on changed surface), B3 (code-review.md), B4 (security-review.md), B5 (lint — no unfilled placeholders + JS/TS ESLint 卡控 via `peaks lint check`. First-time project setup: run `peaks lint baseline` to generate the project-level `.peaks/lint/baseline.json`. Per-slice flow uses `peaks lint check` (= diffOnly: true + baseline waiver + redLineMode: 'baseline-aware'). LLM red-line reference lives at `.peaks/memory/lint-redline-summary.md` (auto-written by `peaks lint check --red-line`); read it BEFORE writing JS/TS so the next slice sees prior sibling-violation context. Project-aware baseline: each project regenerates its own baseline; cross-project reuse is low-value — see `references/jsts-eslint-gate.md` §7), B6 (request-type-sanity), B7 (repair-status — 3-cycle cap), B8 (diff-vs-scope), B9 (perf-baseline).
88
88
 
89
89
  → see `references/rd-transition-gates.md` for the per-gate contract + `ls` / `grep` shell snippets.
90
90