peaks-cli 1.4.1 → 2.0.0
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/.claude-plugin/marketplace.json +51 -0
- package/CHANGELOG.md +238 -0
- package/README-en.md +226 -0
- package/README.md +142 -165
- package/dist/src/cli/commands/agent-commands.d.ts +20 -0
- package/dist/src/cli/commands/agent-commands.js +48 -0
- package/dist/src/cli/commands/audit-commands.d.ts +18 -0
- package/dist/src/cli/commands/audit-commands.js +138 -0
- package/dist/src/cli/commands/classify-classify-commands.d.ts +19 -0
- package/dist/src/cli/commands/classify-classify-commands.js +151 -0
- package/dist/src/cli/commands/code-review-commands.d.ts +34 -0
- package/dist/src/cli/commands/code-review-commands.js +83 -0
- package/dist/src/cli/commands/config-commands.js +90 -0
- package/dist/src/cli/commands/context-commands.d.ts +21 -0
- package/dist/src/cli/commands/context-commands.js +167 -0
- package/dist/src/cli/commands/core-artifact-commands.js +81 -2
- package/dist/src/cli/commands/hook-handle.js +50 -0
- package/dist/src/cli/commands/loop-commands.d.ts +21 -0
- package/dist/src/cli/commands/loop-commands.js +128 -0
- package/dist/src/cli/commands/memory-commands.d.ts +13 -0
- package/dist/src/cli/commands/memory-commands.js +60 -0
- package/dist/src/cli/commands/openspec-commands.js +37 -0
- package/dist/src/cli/commands/preferences-commands.d.ts +2 -0
- package/dist/src/cli/commands/preferences-commands.js +147 -0
- package/dist/src/cli/commands/retrospective-commands.d.ts +9 -0
- package/dist/src/cli/commands/retrospective-commands.js +58 -0
- package/dist/src/cli/commands/skill-conformance-commands.d.ts +9 -0
- package/dist/src/cli/commands/skill-conformance-commands.js +39 -0
- package/dist/src/cli/commands/understand-commands.js +34 -0
- package/dist/src/cli/commands/upgrade-commands.d.ts +23 -0
- package/dist/src/cli/commands/upgrade-commands.js +57 -0
- package/dist/src/cli/commands/workflow-commands.js +70 -0
- package/dist/src/cli/commands/workspace-commands.js +86 -0
- package/dist/src/cli/program.js +46 -22
- package/dist/src/services/agent/ecc-agent-service.d.ts +47 -0
- package/dist/src/services/agent/ecc-agent-service.js +143 -0
- package/dist/src/services/artifacts/request-artifact-service.js +14 -0
- package/dist/src/services/audit/backing-detector.d.ts +24 -0
- package/dist/src/services/audit/backing-detector.js +59 -0
- package/dist/src/services/audit/classifier.d.ts +38 -0
- package/dist/src/services/audit/classifier.js +127 -0
- package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +29 -0
- package/dist/src/services/audit/enforcers/active-skill-resolver.js +71 -0
- package/dist/src/services/audit/enforcers/design-draft-confirm.d.ts +25 -0
- package/dist/src/services/audit/enforcers/design-draft-confirm.js +54 -0
- package/dist/src/services/audit/enforcers/lint-audit-regression.d.ts +21 -0
- package/dist/src/services/audit/enforcers/lint-audit-regression.js +86 -0
- package/dist/src/services/audit/enforcers/lint-catalog-governance.d.ts +27 -0
- package/dist/src/services/audit/enforcers/lint-catalog-governance.js +38 -0
- package/dist/src/services/audit/enforcers/lint-cli-back.d.ts +16 -0
- package/dist/src/services/audit/enforcers/lint-cli-back.js +35 -0
- package/dist/src/services/audit/enforcers/lint-output-style.d.ts +11 -0
- package/dist/src/services/audit/enforcers/lint-output-style.js +94 -0
- package/dist/src/services/audit/enforcers/lint-reference-integrity.d.ts +6 -0
- package/dist/src/services/audit/enforcers/lint-reference-integrity.js +83 -0
- package/dist/src/services/audit/enforcers/lint-reference-shape.d.ts +30 -0
- package/dist/src/services/audit/enforcers/lint-reference-shape.js +272 -0
- package/dist/src/services/audit/enforcers/lint-style.d.ts +49 -0
- package/dist/src/services/audit/enforcers/lint-style.js +173 -0
- package/dist/src/services/audit/enforcers/lint-workflow-shape.d.ts +5 -0
- package/dist/src/services/audit/enforcers/lint-workflow-shape.js +141 -0
- package/dist/src/services/audit/enforcers/login-gate.d.ts +23 -0
- package/dist/src/services/audit/enforcers/login-gate.js +40 -0
- package/dist/src/services/audit/enforcers/mock-placement.d.ts +25 -0
- package/dist/src/services/audit/enforcers/mock-placement.js +48 -0
- package/dist/src/services/audit/enforcers/no-root-pollution.d.ts +21 -0
- package/dist/src/services/audit/enforcers/no-root-pollution.js +56 -0
- package/dist/src/services/audit/enforcers/pre-rd-scan.d.ts +22 -0
- package/dist/src/services/audit/enforcers/pre-rd-scan.js +23 -0
- package/dist/src/services/audit/enforcers/prototype-fidelity.d.ts +25 -0
- package/dist/src/services/audit/enforcers/prototype-fidelity.js +75 -0
- package/dist/src/services/audit/enforcers/resume-detection.d.ts +21 -0
- package/dist/src/services/audit/enforcers/resume-detection.js +52 -0
- package/dist/src/services/audit/enforcers/solo-code-ban.d.ts +23 -0
- package/dist/src/services/audit/enforcers/solo-code-ban.js +27 -0
- package/dist/src/services/audit/enforcers/sub-agent-sid.d.ts +25 -0
- package/dist/src/services/audit/enforcers/sub-agent-sid.js +63 -0
- package/dist/src/services/audit/enforcers/tech-doc-presence.d.ts +28 -0
- package/dist/src/services/audit/enforcers/tech-doc-presence.js +35 -0
- package/dist/src/services/audit/red-line-catalog-p2-a.d.ts +21 -0
- package/dist/src/services/audit/red-line-catalog-p2-a.js +233 -0
- package/dist/src/services/audit/red-line-catalog-p2-b.d.ts +19 -0
- package/dist/src/services/audit/red-line-catalog-p2-b.js +225 -0
- package/dist/src/services/audit/red-line-catalog.d.ts +51 -0
- package/dist/src/services/audit/red-line-catalog.js +210 -0
- package/dist/src/services/audit/red-lines-service.d.ts +23 -0
- package/dist/src/services/audit/red-lines-service.js +486 -0
- package/dist/src/services/audit/scanners/openspec-scanner.d.ts +15 -0
- package/dist/src/services/audit/scanners/openspec-scanner.js +55 -0
- package/dist/src/services/audit/scanners/rules-tree-scanner.d.ts +16 -0
- package/dist/src/services/audit/scanners/rules-tree-scanner.js +56 -0
- package/dist/src/services/audit/scanners/skills-tree-scanner.d.ts +17 -0
- package/dist/src/services/audit/scanners/skills-tree-scanner.js +46 -0
- package/dist/src/services/audit/static-service.d.ts +57 -0
- package/dist/src/services/audit/static-service.js +125 -0
- package/dist/src/services/audit/types.d.ts +69 -0
- package/dist/src/services/audit/types.js +13 -0
- package/dist/src/services/classify/classify-service.d.ts +42 -0
- package/dist/src/services/classify/classify-service.js +122 -0
- package/dist/src/services/classify/classify-types.d.ts +79 -0
- package/dist/src/services/classify/classify-types.js +90 -0
- package/dist/src/services/code-review/ocr-service.d.ts +129 -0
- package/dist/src/services/code-review/ocr-service.js +362 -0
- package/dist/src/services/config/config-migration.d.ts +32 -0
- package/dist/src/services/config/config-migration.js +92 -0
- package/dist/src/services/config/config-restore.d.ts +10 -0
- package/dist/src/services/config/config-restore.js +47 -0
- package/dist/src/services/config/config-rollback.d.ts +13 -0
- package/dist/src/services/config/config-rollback.js +26 -0
- package/dist/src/services/config/config-service.d.ts +35 -2
- package/dist/src/services/config/config-service.js +81 -0
- package/dist/src/services/config/config-types.d.ts +58 -0
- package/dist/src/services/config/config-types.js +6 -0
- package/dist/src/services/doctor/doctor-service.js +96 -0
- package/dist/src/services/fuzzy-matching/fuzzy-match-service.d.ts +15 -0
- package/dist/src/services/fuzzy-matching/fuzzy-match-service.js +56 -0
- package/dist/src/services/fuzzy-matching/types.d.ts +20 -0
- package/dist/src/services/fuzzy-matching/types.js +1 -0
- package/dist/src/services/ide/adapters/hermes-adapter.d.ts +21 -0
- package/dist/src/services/ide/adapters/hermes-adapter.js +51 -0
- package/dist/src/services/ide/adapters/openclaw-adapter.d.ts +14 -0
- package/dist/src/services/ide/adapters/openclaw-adapter.js +42 -0
- package/dist/src/services/ide/ide-registry.js +7 -0
- package/dist/src/services/ide/ide-types.d.ts +1 -1
- package/dist/src/services/memory/memory-search-service.d.ts +61 -0
- package/dist/src/services/memory/memory-search-service.js +80 -0
- package/dist/src/services/openspec/openspec-propose-from-doctor-service.d.ts +31 -0
- package/dist/src/services/openspec/openspec-propose-from-doctor-service.js +95 -0
- package/dist/src/services/preferences/preferences-service.d.ts +6 -0
- package/dist/src/services/preferences/preferences-service.js +43 -0
- package/dist/src/services/preferences/preferences-types.d.ts +90 -0
- package/dist/src/services/preferences/preferences-types.js +38 -0
- package/dist/src/services/recommendations/capability-seed-items.js +0 -1
- package/dist/src/services/recommendations/capability-seed-mappings.js +0 -1
- package/dist/src/services/recommendations/capability-seed-sources.js +0 -1
- package/dist/src/services/retrospective/retrospective-search-service.d.ts +37 -0
- package/dist/src/services/retrospective/retrospective-search-service.js +75 -0
- package/dist/src/services/skills/skill-conformance-service.d.ts +40 -0
- package/dist/src/services/skills/skill-conformance-service.js +136 -0
- package/dist/src/services/skills/skill-runbook-service.js +44 -10
- package/dist/src/services/skills/sync-service.d.ts +43 -0
- package/dist/src/services/skills/sync-service.js +99 -0
- package/dist/src/services/slice/slice-check-service.js +166 -13
- package/dist/src/services/slice/slice-check-types.d.ts +1 -1
- package/dist/src/services/standards/migrate-claude-rules-service.d.ts +19 -0
- package/dist/src/services/standards/migrate-claude-rules-service.js +193 -0
- package/dist/src/services/standards/project-context.d.ts +1 -1
- package/dist/src/services/standards/project-context.js +0 -4
- package/dist/src/services/standards/project-standards-service.js +1 -3
- package/dist/src/services/understand/understand-scan-service.js +15 -2
- package/dist/src/services/understand/understand-types.d.ts +26 -0
- package/dist/src/services/upgrade/1x-detector-service.d.ts +7 -0
- package/dist/src/services/upgrade/1x-detector-service.js +94 -0
- package/dist/src/services/upgrade/gitignore-migrate-service.d.ts +56 -0
- package/dist/src/services/upgrade/gitignore-migrate-service.js +170 -0
- package/dist/src/services/upgrade/upgrade-service.d.ts +47 -0
- package/dist/src/services/upgrade/upgrade-service.js +381 -0
- package/dist/src/services/workspace/migrate-1-4-1-service.js +1 -1
- package/dist/src/services/workspace/sid-naming-guard.d.ts +14 -0
- package/dist/src/services/workspace/sid-naming-guard.js +31 -0
- package/dist/src/services/workspace/workspace-archive-service.d.ts +19 -0
- package/dist/src/services/workspace/workspace-archive-service.js +32 -0
- package/dist/src/services/workspace/workspace-clean-service.d.ts +41 -0
- package/dist/src/services/workspace/workspace-clean-service.js +86 -0
- package/dist/src/services/workspace/workspace-state-service.d.ts +7 -0
- package/dist/src/services/workspace/workspace-state-service.js +43 -0
- package/dist/src/shared/change-id.js +4 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +10 -8
- package/schemas/doctor-report.schema.json +1 -1
- package/scripts/install-skills.mjs +296 -12
- package/skills/peaks-doctor/SKILL.md +59 -0
- package/skills/peaks-doctor/references/doctor-check-catalog.md +31 -0
- package/skills/peaks-doctor/references/from-doctor-flow.md +64 -0
- package/skills/peaks-doctor/test_prompts.json +17 -0
- package/skills/peaks-ide/SKILL.md +2 -0
- package/skills/peaks-qa/SKILL.md +9 -7
- package/skills/peaks-qa/references/artifact-per-request.md +19 -5
- package/skills/peaks-qa/references/qa-perf-test-plan.md +6 -6
- package/skills/peaks-qa/references/qa-runbook.md +1 -1
- package/skills/peaks-rd/SKILL.md +25 -10
- package/skills/peaks-rd/references/ocr-integration.md +214 -0
- package/skills/peaks-rd/references/rd-fanout-contracts.md +70 -0
- package/skills/peaks-rd/references/rd-runbook.md +1 -1
- package/skills/peaks-solo/SKILL.md +11 -5
- package/skills/peaks-solo/references/completion-handoff.md +3 -1
- package/skills/peaks-solo/references/step-0-55-1x-detection.md +82 -0
- package/skills/peaks-solo/references/workflow-gates-and-types.md +9 -0
- package/dist/src/cli/commands/shadcn-commands.d.ts +0 -3
- package/dist/src/cli/commands/shadcn-commands.js +0 -35
- package/dist/src/cli/commands/skill-context-stats-command.d.ts +0 -40
- package/dist/src/cli/commands/skill-context-stats-command.js +0 -96
- package/dist/src/cli/commands/skill-scope-commands.d.ts +0 -51
- package/dist/src/cli/commands/skill-scope-commands.js +0 -310
- package/dist/src/services/shadcn/shadcn-service.d.ts +0 -27
- package/dist/src/services/shadcn/shadcn-service.js +0 -128
- package/dist/src/services/skill-scope/adapters/_stub-helper.d.ts +0 -39
- package/dist/src/services/skill-scope/adapters/_stub-helper.js +0 -98
- package/dist/src/services/skill-scope/adapters/claude-code.d.ts +0 -59
- package/dist/src/services/skill-scope/adapters/claude-code.js +0 -304
- package/dist/src/services/skill-scope/adapters/codex.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/codex.js +0 -12
- package/dist/src/services/skill-scope/adapters/cursor.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/cursor.js +0 -13
- package/dist/src/services/skill-scope/adapters/qoder.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/qoder.js +0 -13
- package/dist/src/services/skill-scope/adapters/tongyi.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/tongyi.js +0 -13
- package/dist/src/services/skill-scope/adapters/trae.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/trae.js +0 -12
- package/dist/src/services/skill-scope/detect.d.ts +0 -81
- package/dist/src/services/skill-scope/detect.js +0 -513
- package/dist/src/services/skill-scope/registry.d.ts +0 -41
- package/dist/src/services/skill-scope/registry.js +0 -83
- package/dist/src/services/skill-scope/source-of-truth.d.ts +0 -44
- package/dist/src/services/skill-scope/source-of-truth.js +0 -118
- package/dist/src/services/skill-scope/types.d.ts +0 -195
- package/dist/src/services/skill-scope/types.js +0 -97
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill-conformance audit (Slice #12) — checks every peaks-* SKILL.md
|
|
3
|
+
* against the 5 standards from the L1+L2+L3 redesign §5.4 Slice #12:
|
|
4
|
+
*
|
|
5
|
+
* 1. task-level frontmatter (all 12 skills must declare one)
|
|
6
|
+
* 2. CLI-back 注解 coverage (each skill body must document which
|
|
7
|
+
* `peaks <cmd>` commands it composes; the absence is a SKILL.md
|
|
8
|
+
* anti-pattern — it means the LLM has to discover the CLI
|
|
9
|
+
* primitives by accident)
|
|
10
|
+
* 3. loadStrategy on-demand 标注 (skills should declare when they
|
|
11
|
+
* load — `eager` for always-loaded, `on-demand` for invoked)
|
|
12
|
+
* 4. 800-line cap (Karpathy limit per spec §2.3)
|
|
13
|
+
* 5. outputStyle: peaks-concise-v1 frontmatter (peak-cli display
|
|
14
|
+
* style for the skill's user-visible output)
|
|
15
|
+
*
|
|
16
|
+
* Plus 1 derived check:
|
|
17
|
+
* 6. CLI primitives declared in references/audit/ (Skill must
|
|
18
|
+
* surface every peaks <cmd> it composes in the references/ subdir;
|
|
19
|
+
* this is the "CLI-back 注解 100% 覆盖" check)
|
|
20
|
+
*/
|
|
21
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
const SKILL_NAMES = [
|
|
25
|
+
'peaks-ide',
|
|
26
|
+
'peaks-prd',
|
|
27
|
+
'peaks-ui',
|
|
28
|
+
'peaks-rd',
|
|
29
|
+
'peaks-qa',
|
|
30
|
+
'peaks-sc',
|
|
31
|
+
'peaks-solo',
|
|
32
|
+
'peaks-solo-resume',
|
|
33
|
+
'peaks-solo-status',
|
|
34
|
+
'peaks-solo-test',
|
|
35
|
+
'peaks-sop',
|
|
36
|
+
'peaks-txt',
|
|
37
|
+
'peaks-doctor'
|
|
38
|
+
];
|
|
39
|
+
const REQUIRED_FRONTMATTER_FIELDS = ['name', 'description'];
|
|
40
|
+
const OPTIONAL_FRONTMATTER_FIELDS = ['task-level', 'loadStrategy', 'outputStyle'];
|
|
41
|
+
const MAX_LINE_COUNT = 800;
|
|
42
|
+
const CLI_BACK_PATTERNS = [
|
|
43
|
+
/`peaks\s+[a-z][a-z0-9-]+(?:\s+[a-z][a-z0-9-]+)*`/g,
|
|
44
|
+
/`peaks\s+[a-z][a-z0-9-]+/g,
|
|
45
|
+
];
|
|
46
|
+
function readSkillFrontmatter(skillPath) {
|
|
47
|
+
if (!existsSync(skillPath))
|
|
48
|
+
return null;
|
|
49
|
+
const content = readFileSync(skillPath, 'utf-8');
|
|
50
|
+
const match = /^---\n([\s\S]*?)\n---\n/.exec(content);
|
|
51
|
+
if (match === null)
|
|
52
|
+
return { raw: content, fields: {} };
|
|
53
|
+
const raw = match[1] ?? '';
|
|
54
|
+
const fields = {};
|
|
55
|
+
for (const line of raw.split('\n')) {
|
|
56
|
+
const colonIdx = line.indexOf(':');
|
|
57
|
+
if (colonIdx === -1)
|
|
58
|
+
continue;
|
|
59
|
+
const key = line.slice(0, colonIdx).trim();
|
|
60
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
61
|
+
if (key.length > 0)
|
|
62
|
+
fields[key] = value;
|
|
63
|
+
}
|
|
64
|
+
return { raw, fields };
|
|
65
|
+
}
|
|
66
|
+
function lineCount(filePath) {
|
|
67
|
+
try {
|
|
68
|
+
return statSync(filePath).size > 0 ? readFileSync(filePath, 'utf-8').split(/\r?\n/).length : 0;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function countCliBackReferences(content) {
|
|
75
|
+
let count = 0;
|
|
76
|
+
for (const pattern of CLI_BACK_PATTERNS) {
|
|
77
|
+
pattern.lastIndex = 0;
|
|
78
|
+
const matches = content.match(pattern);
|
|
79
|
+
if (matches)
|
|
80
|
+
count += matches.length;
|
|
81
|
+
}
|
|
82
|
+
return count;
|
|
83
|
+
}
|
|
84
|
+
export function auditSkillConformance(input) {
|
|
85
|
+
const skillsDir = join(input.projectRoot, 'skills');
|
|
86
|
+
const checks = [];
|
|
87
|
+
for (const skillName of SKILL_NAMES) {
|
|
88
|
+
const skillPath = join(skillsDir, skillName, 'SKILL.md');
|
|
89
|
+
const fm = readSkillFrontmatter(skillPath);
|
|
90
|
+
// 1. task-level frontmatter
|
|
91
|
+
if (fm === null) {
|
|
92
|
+
checks.push({ id: 'frontmatter:present', skill: skillName, level: 'fail', message: 'SKILL.md missing' });
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
for (const required of REQUIRED_FRONTMATTER_FIELDS) {
|
|
96
|
+
if (fm.fields[required] === undefined) {
|
|
97
|
+
checks.push({ id: `frontmatter:${required}`, skill: skillName, level: 'fail', message: `frontmatter missing required field "${required}"` });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// 2. CLI-back annotation
|
|
102
|
+
if (fm !== null) {
|
|
103
|
+
const content = readFileSync(skillPath, 'utf-8');
|
|
104
|
+
const cliCount = countCliBackReferences(content);
|
|
105
|
+
if (cliCount === 0) {
|
|
106
|
+
checks.push({ id: 'cli-back:present', skill: skillName, level: 'warn', message: 'no `peaks <cmd>` references in SKILL.md body; consider documenting which CLI primitives the skill composes' });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// 3. loadStrategy on-demand 标注
|
|
110
|
+
if (fm !== null && fm.fields['loadStrategy'] === undefined) {
|
|
111
|
+
checks.push({ id: 'loadStrategy:declared', skill: skillName, level: 'warn', message: 'loadStrategy not declared in frontmatter (eager | on-demand)' });
|
|
112
|
+
}
|
|
113
|
+
// 4. 800-line cap
|
|
114
|
+
if (fm !== null) {
|
|
115
|
+
const lines = lineCount(skillPath);
|
|
116
|
+
if (lines > MAX_LINE_COUNT) {
|
|
117
|
+
checks.push({ id: 'line-count:cap', skill: skillName, level: 'fail', message: `${lines} lines > ${MAX_LINE_COUNT} cap (Karpathy)` });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// 5. outputStyle: peaks-concise-v1
|
|
121
|
+
if (fm !== null && fm.fields['outputStyle'] === undefined) {
|
|
122
|
+
checks.push({ id: 'outputStyle:declared', skill: skillName, level: 'warn', message: 'outputStyle not declared in frontmatter' });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const failed = checks.filter((c) => c.level === 'fail').length;
|
|
126
|
+
const warned = checks.filter((c) => c.level === 'warn').length;
|
|
127
|
+
const passed = checks.filter((c) => c.level === 'pass').length;
|
|
128
|
+
return {
|
|
129
|
+
checked: checks.length,
|
|
130
|
+
passed,
|
|
131
|
+
warned,
|
|
132
|
+
failed,
|
|
133
|
+
checks,
|
|
134
|
+
summary: failed === 0 ? 'all hard checks pass; warnings are advisory' : `${failed} hard failure(s); fix before shipping`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dirname, join } from 'node:path';
|
|
2
|
+
import * as path from 'node:path';
|
|
2
3
|
import { readText } from '../../shared/fs.js';
|
|
3
4
|
import { loadSkillRegistry } from './skill-registry.js';
|
|
4
5
|
const DESTRUCTIVE_APPLY_PATTERNS = [
|
|
@@ -12,8 +13,25 @@ const DESTRUCTIVE_APPLY_PATTERNS = [
|
|
|
12
13
|
const AUTHORIZATION_KEYWORDS_PATTERN = /authoriz|explicit|--dry-run|approv|only after|only when/i;
|
|
13
14
|
const PEAKS_COMMAND_LINE_PATTERN = /^\s*peaks\s+\w/;
|
|
14
15
|
function extractRunbookSection(body) {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
// Find a `## Default runbook` heading at the start of a line, then capture
|
|
17
|
+
// the section body up to the next `## ` heading or end of input.
|
|
18
|
+
//
|
|
19
|
+
// Implementation note: we avoid a regex with a multiline `(?=\n## |$)`
|
|
20
|
+
// lookahead because the `m` flag turns `$` into "end of any line" in
|
|
21
|
+
// JavaScript, which would let the lazy capture stop at the first `\n`.
|
|
22
|
+
// Instead, we (a) use the regex only to locate the heading, then (b)
|
|
23
|
+
// manually scan forward for the next `## ` heading or end of input.
|
|
24
|
+
const headingRe = /^## Default runbook[^\n]*(?:\n|$)/m;
|
|
25
|
+
const headingMatch = headingRe.exec(body);
|
|
26
|
+
if (headingMatch === null)
|
|
27
|
+
return null;
|
|
28
|
+
const startAfter = headingMatch.index + headingMatch[0].length;
|
|
29
|
+
const rest = body.slice(startAfter);
|
|
30
|
+
// Find the next `## ` heading at the start of a line.
|
|
31
|
+
const nextHeadingRe = /^## /m;
|
|
32
|
+
const nextMatch = nextHeadingRe.exec(rest);
|
|
33
|
+
const end = nextMatch === null ? rest.length : nextMatch.index;
|
|
34
|
+
return rest.slice(0, end);
|
|
17
35
|
}
|
|
18
36
|
/**
|
|
19
37
|
* Load the runbook section, falling back to `references/runbook.md` if the
|
|
@@ -32,15 +50,31 @@ function extractRunbookSection(body) {
|
|
|
32
50
|
*/
|
|
33
51
|
async function loadRunbookSection(skillPath, body) {
|
|
34
52
|
const inline = extractRunbookSection(body);
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
const skillDir = dirname(skillPath);
|
|
54
|
+
// Try multiple reference filenames. Convention: each skill may name its
|
|
55
|
+
// runbook file `runbook.md` (the generic name, used by peaks-solo), or
|
|
56
|
+
// `<role>-runbook.md` (the role-suffixed name, used by peaks-rd → rd-runbook.md,
|
|
57
|
+
// peaks-qa → qa-runbook.md, etc.). Prefer the longest extracted section.
|
|
58
|
+
const refCandidates = [
|
|
59
|
+
join(skillDir, 'references', 'runbook.md'),
|
|
60
|
+
join(skillDir, 'references', `${path.basename(skillDir).replace(/^peaks-/, '')}-runbook.md`)
|
|
61
|
+
];
|
|
62
|
+
let bestRef = null;
|
|
63
|
+
for (const refPath of refCandidates) {
|
|
64
|
+
try {
|
|
65
|
+
const refBody = await readText(refPath);
|
|
66
|
+
const refSection = extractRunbookSection(refBody);
|
|
67
|
+
if (refSection === null)
|
|
68
|
+
continue;
|
|
69
|
+
if (bestRef === null || refSection.length > bestRef.length) {
|
|
70
|
+
bestRef = refSection;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// candidate not present or unreadable; try the next one
|
|
75
|
+
}
|
|
43
76
|
}
|
|
77
|
+
const refSection = bestRef;
|
|
44
78
|
if (inline === null)
|
|
45
79
|
return refSection;
|
|
46
80
|
if (refSection === null)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { IdeId } from '../ide/ide-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The 8 platforms per Slice #12 final piece. Slice #0.7 + Slice
|
|
4
|
+
* #0.5.2 registered these in the IdeId union; this list is the
|
|
5
|
+
* single source of truth for the sync fan-out.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SYNC_PLATFORMS: readonly IdeId[];
|
|
8
|
+
export interface PlatformSyncResult {
|
|
9
|
+
/** The platform that was attempted. */
|
|
10
|
+
readonly platform: IdeId;
|
|
11
|
+
/** True if installBundledSkills returned without error. */
|
|
12
|
+
readonly ok: boolean;
|
|
13
|
+
/** Skills newly symlinked (idempotent re-runs return []). */
|
|
14
|
+
readonly installed: readonly string[];
|
|
15
|
+
/** Skills whose target was not a managed symlink (third-party owned). */
|
|
16
|
+
readonly skipped: readonly string[];
|
|
17
|
+
/** Error message; present when ok=false. */
|
|
18
|
+
readonly error?: string;
|
|
19
|
+
/** Wall-clock duration in ms. */
|
|
20
|
+
readonly durationMs: number;
|
|
21
|
+
}
|
|
22
|
+
export interface SyncServiceInput {
|
|
23
|
+
readonly projectRoot: string;
|
|
24
|
+
/** When omitted, the service iterates all 8 platforms. */
|
|
25
|
+
readonly platforms?: readonly IdeId[] | undefined;
|
|
26
|
+
/** When true, the installer is invoked in dry-run mode. */
|
|
27
|
+
readonly dryRun?: boolean | undefined;
|
|
28
|
+
}
|
|
29
|
+
export interface SyncServiceResult {
|
|
30
|
+
readonly applied: boolean;
|
|
31
|
+
readonly dryRun: boolean;
|
|
32
|
+
readonly projectRoot: string;
|
|
33
|
+
readonly perPlatform: readonly PlatformSyncResult[];
|
|
34
|
+
readonly syncedCount: number;
|
|
35
|
+
readonly failedCount: number;
|
|
36
|
+
readonly totalInstalled: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Validate a single platform id against the SYNC_PLATFORMS
|
|
40
|
+
* allowlist. Throws on a bogus value.
|
|
41
|
+
*/
|
|
42
|
+
export declare function assertValidPlatform(platform: string): asserts platform is IdeId;
|
|
43
|
+
export declare function runSkillSync(input: SyncServiceInput): Promise<SyncServiceResult>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* peaks skill sync — fan-out the peaks-* skill family to all 8
|
|
3
|
+
* supported LLM-CLI platforms. Per spec §9 line 1105 (Slice #12
|
|
4
|
+
* final piece): "peaks skills sync 8 平台分发".
|
|
5
|
+
*
|
|
6
|
+
* The 8 target platforms are enumerated by the `IdeId` union
|
|
7
|
+
* (src/services/ide/ide-types.ts:16-24). The per-IDE install
|
|
8
|
+
* profile is `IdeSkillInstall`; the actual symlink installer
|
|
9
|
+
* is `scripts/install-skills.mjs::installBundledSkills` (dynamically
|
|
10
|
+
* imported so this module does not require a build step).
|
|
11
|
+
*/
|
|
12
|
+
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
/**
|
|
15
|
+
* The 8 platforms per Slice #12 final piece. Slice #0.7 + Slice
|
|
16
|
+
* #0.5.2 registered these in the IdeId union; this list is the
|
|
17
|
+
* single source of truth for the sync fan-out.
|
|
18
|
+
*/
|
|
19
|
+
export const SYNC_PLATFORMS = [
|
|
20
|
+
'claude-code',
|
|
21
|
+
'trae',
|
|
22
|
+
'codex',
|
|
23
|
+
'cursor',
|
|
24
|
+
'qoder',
|
|
25
|
+
'tongyi-lingma',
|
|
26
|
+
'hermes',
|
|
27
|
+
'openclaw',
|
|
28
|
+
];
|
|
29
|
+
let cachedInstaller = null;
|
|
30
|
+
async function loadInstaller() {
|
|
31
|
+
if (cachedInstaller !== null)
|
|
32
|
+
return cachedInstaller;
|
|
33
|
+
const scriptPath = join(process.cwd(), 'scripts', 'install-skills.mjs');
|
|
34
|
+
const mod = (await import(pathToFileURL(scriptPath).href));
|
|
35
|
+
cachedInstaller = mod.installBundledSkills;
|
|
36
|
+
return cachedInstaller;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Validate a single platform id against the SYNC_PLATFORMS
|
|
40
|
+
* allowlist. Throws on a bogus value.
|
|
41
|
+
*/
|
|
42
|
+
export function assertValidPlatform(platform) {
|
|
43
|
+
if (!SYNC_PLATFORMS.includes(platform)) {
|
|
44
|
+
throw new Error(`peaks skill sync: unknown platform "${platform}". ` +
|
|
45
|
+
`Valid platforms: ${SYNC_PLATFORMS.join(', ')}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function runSkillSync(input) {
|
|
49
|
+
const platforms = input.platforms ?? SYNC_PLATFORMS;
|
|
50
|
+
for (const p of platforms) {
|
|
51
|
+
assertValidPlatform(p);
|
|
52
|
+
}
|
|
53
|
+
const dryRun = input.dryRun === true;
|
|
54
|
+
const installer = await loadInstaller();
|
|
55
|
+
const perPlatform = [];
|
|
56
|
+
let syncedCount = 0;
|
|
57
|
+
let failedCount = 0;
|
|
58
|
+
let totalInstalled = 0;
|
|
59
|
+
for (const platform of platforms) {
|
|
60
|
+
const start = Date.now();
|
|
61
|
+
try {
|
|
62
|
+
const result = installer({
|
|
63
|
+
ideId: platform,
|
|
64
|
+
projectRoot: input.projectRoot,
|
|
65
|
+
...(dryRun ? { dryRun: true } : {}),
|
|
66
|
+
});
|
|
67
|
+
perPlatform.push({
|
|
68
|
+
platform,
|
|
69
|
+
ok: true,
|
|
70
|
+
installed: result.installed,
|
|
71
|
+
skipped: result.skipped,
|
|
72
|
+
durationMs: Date.now() - start,
|
|
73
|
+
});
|
|
74
|
+
syncedCount += 1;
|
|
75
|
+
totalInstalled += result.installed.length;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
79
|
+
perPlatform.push({
|
|
80
|
+
platform,
|
|
81
|
+
ok: false,
|
|
82
|
+
installed: [],
|
|
83
|
+
skipped: [],
|
|
84
|
+
error: message,
|
|
85
|
+
durationMs: Date.now() - start,
|
|
86
|
+
});
|
|
87
|
+
failedCount += 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
applied: !dryRun,
|
|
92
|
+
dryRun,
|
|
93
|
+
projectRoot: input.projectRoot,
|
|
94
|
+
perPlatform,
|
|
95
|
+
syncedCount,
|
|
96
|
+
failedCount,
|
|
97
|
+
totalInstalled,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -1,17 +1,55 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { isDirectory } from '../../shared/fs.js';
|
|
5
5
|
import { getCurrentChangeId } from '../../shared/change-id.js';
|
|
6
6
|
import { verifyPipeline } from '../workflow/pipeline-verify-service.js';
|
|
7
|
-
|
|
7
|
+
import { findMockViolations } from '../audit/enforcers/mock-placement.js';
|
|
8
|
+
import { runRedLinesAudit } from '../audit/red-lines-service.js';
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a CLI binary to a project-local path, falling back to
|
|
11
|
+
* the system `npx`. pnpm (and npm/yarn) all create
|
|
12
|
+
* `node_modules/.bin/<name>`:
|
|
13
|
+
*
|
|
14
|
+
* - On Unix, this is a symlink to the package's executable.
|
|
15
|
+
* - On Windows, this is a `.cmd` shim; `execFileSync` only
|
|
16
|
+
* resolves `.cmd` through the shell (PATHEXT), so we pass
|
|
17
|
+
* `shell: true` when invoking one. Without this, the
|
|
18
|
+
* Windows `npx ENOENT` false-positive from
|
|
19
|
+
* observations 2317 + 2792 reproduces for every local
|
|
20
|
+
* binary.
|
|
21
|
+
*
|
|
22
|
+
* Returns the command + args + a `shell` flag that the
|
|
23
|
+
* `runCommand` helper threads into `execFileSync`.
|
|
24
|
+
*/
|
|
25
|
+
function resolveLocalBinary(projectRoot, name) {
|
|
26
|
+
// pnpm creates `node_modules/.bin/<name>` (symlink on Unix,
|
|
27
|
+
// `.cmd` shim on Windows). We probe both shapes; the
|
|
28
|
+
// `process.platform === 'win32'` extension probe is the most
|
|
29
|
+
// portable approach.
|
|
30
|
+
const isWin = process.platform === 'win32';
|
|
31
|
+
const candidateNames = isWin ? [`${name}.cmd`, `${name}.ps1`, `${name}`] : [name];
|
|
32
|
+
for (const candidate of candidateNames) {
|
|
33
|
+
const cmdPath = join(projectRoot, 'node_modules', '.bin', candidate);
|
|
34
|
+
if (existsSync(cmdPath)) {
|
|
35
|
+
return { command: cmdPath, args: [], shell: isWin };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Fallback: system npx. On Windows this still has the ENOENT
|
|
39
|
+
// issue, but the fallback is at least informative when it
|
|
40
|
+
// fires (the user can see "npx not found" instead of a
|
|
41
|
+
// silent exit 1).
|
|
42
|
+
return { command: 'npx', args: [name], shell: false };
|
|
43
|
+
}
|
|
44
|
+
function runCommand(command, args, cwd, timeoutMs, shell = false) {
|
|
8
45
|
const start = Date.now();
|
|
9
46
|
try {
|
|
10
47
|
const stdout = execFileSync(command, args, {
|
|
11
48
|
cwd,
|
|
12
49
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
13
50
|
timeout: timeoutMs,
|
|
14
|
-
maxBuffer: 32 * 1024 * 1024
|
|
51
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
52
|
+
shell
|
|
15
53
|
}).toString('utf8');
|
|
16
54
|
return {
|
|
17
55
|
status: 'pass',
|
|
@@ -41,11 +79,17 @@ function tailLines(text, max) {
|
|
|
41
79
|
}
|
|
42
80
|
async function runTypecheck(projectRoot) {
|
|
43
81
|
const start = Date.now();
|
|
44
|
-
|
|
82
|
+
// Per Windows npx ENOENT (observations 2317+2792 from
|
|
83
|
+
// 2026-06-09), prefer the project-local `node_modules/.bin/tsc`
|
|
84
|
+
// (symlink on Unix, .cmd on Windows). The local binary is
|
|
85
|
+
// installed by pnpm at workspace-install time and avoids the
|
|
86
|
+
// npx PATH-lookup issue.
|
|
87
|
+
const tsc = resolveLocalBinary(projectRoot, 'tsc');
|
|
88
|
+
const result = runCommand(tsc.command, [...tsc.args, '--noEmit'], projectRoot, 180_000, tsc.shell);
|
|
45
89
|
const testFiles = result.stdout.match(/(tests?\/.*\.test\.ts)/g) ?? [];
|
|
46
90
|
return {
|
|
47
91
|
name: 'typecheck',
|
|
48
|
-
description:
|
|
92
|
+
description: `${tsc.command} --noEmit (no JS emit, type-only check)`,
|
|
49
93
|
status: result.status,
|
|
50
94
|
durationMs: result.durationMs,
|
|
51
95
|
detail: result.status === 'pass'
|
|
@@ -76,13 +120,17 @@ async function runUnitTests(projectRoot, runTests) {
|
|
|
76
120
|
// state. Opt-in to the full suite via `runTests: true` (CLI flag
|
|
77
121
|
// `--run-tests`). See `references/runbook.md` for the rationale and
|
|
78
122
|
// `tests/unit/slice-check-service.test.ts` for the regression net.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
123
|
+
// Per Windows npx ENOENT (observations 2317+2792), resolve
|
|
124
|
+
// the project-local vitest binary instead of shelling out
|
|
125
|
+
// through npx.
|
|
126
|
+
const vitest = resolveLocalBinary(projectRoot, 'vitest');
|
|
127
|
+
const vitestArgs = runTests
|
|
128
|
+
? ['run', '--reporter=default', '--coverage=false']
|
|
129
|
+
: ['run', '--changed', '--reporter=default', '--coverage=false'];
|
|
82
130
|
const description = runTests
|
|
83
|
-
?
|
|
84
|
-
:
|
|
85
|
-
const result = runCommand(
|
|
131
|
+
? `${vitest.command} run (full test suite, coverage off)`
|
|
132
|
+
: `${vitest.command} run --changed (tests for git-changed files only, coverage off)`;
|
|
133
|
+
const result = runCommand(vitest.command, [...vitest.args, ...vitestArgs], projectRoot, 600_000, vitest.shell);
|
|
86
134
|
const summary = parseVitestSummary(result.stdout, result.durationMs);
|
|
87
135
|
// Vitest doesn't always print the per-bucket counts cleanly; infer "passed"
|
|
88
136
|
// as total - failed - skipped when failed/skipped buckets are present.
|
|
@@ -225,7 +273,7 @@ export async function sliceCheck(options) {
|
|
|
225
273
|
if (options.skipTests) {
|
|
226
274
|
stages.push({
|
|
227
275
|
name: 'unit-tests',
|
|
228
|
-
description: '
|
|
276
|
+
description: 'vitest run (skipped per --skip-tests)',
|
|
229
277
|
status: 'skipped',
|
|
230
278
|
durationMs: 0,
|
|
231
279
|
detail: 'Skipped: --skip-tests was set. Use the peaks-solo-test skill to run the full suite manually.'
|
|
@@ -244,7 +292,7 @@ export async function sliceCheck(options) {
|
|
|
244
292
|
const failureCount = unitTests.data?.failed ?? 0;
|
|
245
293
|
stages.push({
|
|
246
294
|
name: 'unit-tests',
|
|
247
|
-
description: `
|
|
295
|
+
description: `vitest run ${options.runTests === true ? '' : '--changed '} (overridden via --allow-pre-existing-failures)`.trim(),
|
|
248
296
|
status: 'skipped',
|
|
249
297
|
durationMs: unitTests.durationMs,
|
|
250
298
|
detail: `pre-existing failures: ${failureCount} failing test(s) under coverage.exclude or unrelated to this slice; user opted in via --allow-pre-existing-failures. For the long-term fix, mark these tests .skip or move to coverage.exclude (see dogfood-2-f1-f4.md F17c).`,
|
|
@@ -261,6 +309,17 @@ export async function sliceCheck(options) {
|
|
|
261
309
|
stages.push(await runReviewFanout(options.projectRoot, rid, options.refreshFanout));
|
|
262
310
|
// Stage 4: gate verify-pipeline
|
|
263
311
|
stages.push(await runGateVerifyPipeline(options.projectRoot, rid, rid));
|
|
312
|
+
// Stage 5: mock-placement (L2.1 P0 #5) — refuse inline mock data in src/ or skills/.
|
|
313
|
+
// Lifts changed files via `git diff --name-only HEAD`; falls back to a
|
|
314
|
+
// warning when the diff is empty (e.g. a fresh tree). Lighter than the
|
|
315
|
+
// full `peaks scan diff-vs-scope` and keeps the slice check self-contained.
|
|
316
|
+
stages.push(await runMockPlacement(options.projectRoot));
|
|
317
|
+
// Stage 6 (Slice #7 L2.4 P2-b): audit-regression — assert
|
|
318
|
+
// catalog integrity (no orphan enforcers, no orphan catalog
|
|
319
|
+
// entries), catalog size lower bound, and runtime budget.
|
|
320
|
+
// The stage runs `peaks audit red-lines` in-process (no
|
|
321
|
+
// subprocess) and is gating: failure exits non-zero.
|
|
322
|
+
stages.push(await runAuditRegression(options.projectRoot));
|
|
264
323
|
const boundaryReady = stages.every((s) => s.status === 'pass' || s.status === 'skipped');
|
|
265
324
|
const nextActions = [];
|
|
266
325
|
if (!boundaryReady) {
|
|
@@ -283,3 +342,97 @@ export async function sliceCheck(options) {
|
|
|
283
342
|
nextActions
|
|
284
343
|
};
|
|
285
344
|
}
|
|
345
|
+
async function runAuditRegression(projectRoot) {
|
|
346
|
+
const start = Date.now();
|
|
347
|
+
try {
|
|
348
|
+
const result = runRedLinesAudit({ projectRoot });
|
|
349
|
+
const durationMs = Date.now() - start;
|
|
350
|
+
// Slice #7 L2.4 P2-b acceptance A3 + A4:
|
|
351
|
+
// - totalRedLines >= 60 (catalog grew to 66; pins the lower bound)
|
|
352
|
+
// - enforcerFindings has no rl-audit-no-orphan-enforcer / rl-audit-no-orphan-catalog hits
|
|
353
|
+
const issues = [];
|
|
354
|
+
if (result.audit.totalRedLines < 60) {
|
|
355
|
+
issues.push(`totalRedLines ${result.audit.totalRedLines} < 60`);
|
|
356
|
+
}
|
|
357
|
+
const orphanFindings = result.audit.enforcerFindings.filter((f) => f.enforcerId === 'rl-audit-no-orphan-enforcer-001' ||
|
|
358
|
+
f.enforcerId === 'rl-audit-no-orphan-catalog-001');
|
|
359
|
+
if (orphanFindings.length > 0) {
|
|
360
|
+
issues.push(`${orphanFindings.length} orphan-enforcer / orphan-catalog finding(s)`);
|
|
361
|
+
}
|
|
362
|
+
if (issues.length > 0) {
|
|
363
|
+
return {
|
|
364
|
+
name: 'audit-regression',
|
|
365
|
+
description: 'audit-regression: catalog integrity + runtime budget (L2.4 P2-b stage 6)',
|
|
366
|
+
status: 'fail',
|
|
367
|
+
durationMs,
|
|
368
|
+
detail: issues.join('; '),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
name: 'audit-regression',
|
|
373
|
+
description: 'audit-regression: catalog integrity + runtime budget (L2.4 P2-b stage 6)',
|
|
374
|
+
status: 'pass',
|
|
375
|
+
durationMs,
|
|
376
|
+
detail: `catalog: ${result.audit.totalRedLines} entries (${result.audit.cliBacked} cli-backed, ${result.audit.proseOnly} prose-only); audit ran in ${durationMs}ms`,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
return {
|
|
381
|
+
name: 'audit-regression',
|
|
382
|
+
description: 'audit-regression: catalog integrity + runtime budget (L2.4 P2-b stage 6)',
|
|
383
|
+
status: 'fail',
|
|
384
|
+
durationMs: Date.now() - start,
|
|
385
|
+
detail: 'audit-regression failed: ' + (error?.message ?? String(error)),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async function runMockPlacement(projectRoot) {
|
|
390
|
+
const start = Date.now();
|
|
391
|
+
// List changed files via git. `--name-only` produces one path per line;
|
|
392
|
+
// we filter to text files in scope and read each.
|
|
393
|
+
const diffResult = runCommand('git', ['diff', '--name-only', '--diff-filter=ACMR', 'HEAD'], projectRoot, 30_000);
|
|
394
|
+
if (diffResult.status !== 'pass') {
|
|
395
|
+
return {
|
|
396
|
+
name: 'mock-placement',
|
|
397
|
+
description: 'mock-placement: no inline mock data in src/ or skills/ (L2.1 P0 #5)',
|
|
398
|
+
status: 'skipped',
|
|
399
|
+
durationMs: Date.now() - start,
|
|
400
|
+
detail: 'git diff failed or returned no changed files; mock-placement scan skipped.'
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
const changed = diffResult.stdout
|
|
404
|
+
.split('\n')
|
|
405
|
+
.map((l) => l.trim())
|
|
406
|
+
.filter(Boolean);
|
|
407
|
+
if (changed.length === 0) {
|
|
408
|
+
return {
|
|
409
|
+
name: 'mock-placement',
|
|
410
|
+
description: 'mock-placement: no inline mock data in src/ or skills/ (L2.1 P0 #5)',
|
|
411
|
+
status: 'skipped',
|
|
412
|
+
durationMs: Date.now() - start,
|
|
413
|
+
detail: 'no changed files in HEAD diff; mock-placement scan skipped.'
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const files = changed
|
|
417
|
+
.filter((p) => p.startsWith('src/') || p.startsWith('skills/'))
|
|
418
|
+
.filter((p) => p.endsWith('.ts') || p.endsWith('.tsx') || p.endsWith('.js') || p.endsWith('.mjs'))
|
|
419
|
+
.map((filePath) => {
|
|
420
|
+
const abs = join(projectRoot, filePath);
|
|
421
|
+
if (!existsSync(abs))
|
|
422
|
+
return null;
|
|
423
|
+
const content = readFileSync(abs, 'utf-8');
|
|
424
|
+
return { filePath, content };
|
|
425
|
+
})
|
|
426
|
+
.filter((f) => f !== null);
|
|
427
|
+
const violations = findMockViolations(files);
|
|
428
|
+
return {
|
|
429
|
+
name: 'mock-placement',
|
|
430
|
+
description: 'mock-placement: no inline mock data in src/ or skills/ (L2.1 P0 #5)',
|
|
431
|
+
status: violations.length === 0 ? 'pass' : 'fail',
|
|
432
|
+
durationMs: Date.now() - start,
|
|
433
|
+
detail: violations.length === 0
|
|
434
|
+
? `Scanned ${files.length} changed file(s); no inline mock data found.`
|
|
435
|
+
: `${violations.length} violation(s): ${violations.map((v) => `${v.filePath} (${v.snippet})`).join('; ')}`,
|
|
436
|
+
data: { scannedFiles: files.length, violations: violations.map((v) => ({ filePath: v.filePath, pattern: v.pattern, snippet: v.snippet })) }
|
|
437
|
+
};
|
|
438
|
+
}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
export type SliceCheckStageStatus = 'pass' | 'fail' | 'skipped';
|
|
29
29
|
export type SliceCheckStage = {
|
|
30
30
|
/** Stable id for the stage (matches the runbook's check list). */
|
|
31
|
-
name: 'typecheck' | 'unit-tests' | 'review-fanout' | 'gate-verify-pipeline';
|
|
31
|
+
name: 'typecheck' | 'unit-tests' | 'review-fanout' | 'gate-verify-pipeline' | 'mock-placement' | 'audit-regression';
|
|
32
32
|
/** Human-readable description. */
|
|
33
33
|
description: string;
|
|
34
34
|
status: SliceCheckStageStatus;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface MigrateClaudeRulesInput {
|
|
2
|
+
readonly projectRoot: string;
|
|
3
|
+
readonly apply?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface MigrateClaudeRulesData {
|
|
6
|
+
readonly backupPath: string | null;
|
|
7
|
+
readonly thinnedFiles: readonly string[];
|
|
8
|
+
readonly scaffoldedFiles: readonly string[];
|
|
9
|
+
readonly preservedFiles: readonly string[];
|
|
10
|
+
readonly wouldChange: boolean;
|
|
11
|
+
readonly applied: boolean;
|
|
12
|
+
readonly nextActions: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface MigrateClaudeRulesResult {
|
|
15
|
+
readonly ok: true;
|
|
16
|
+
readonly data: MigrateClaudeRulesData;
|
|
17
|
+
readonly warnings: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
export declare function migrateClaudeRules(input: MigrateClaudeRulesInput): MigrateClaudeRulesResult;
|