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
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
|
-
import { resolve } from 'node:path';
|
|
5
|
-
const SHADCN_PACKAGE_NAME = 'shadcn';
|
|
6
|
-
const SHADCN_PACKAGE_VERSION = '4.7.0';
|
|
7
|
-
const SHADCN_EXECUTABLE = process.execPath;
|
|
8
|
-
const SHADCN_BINARY_PATH = resolveShadcnBinaryPath();
|
|
9
|
-
const SHADCN_PROCESS_TIMEOUT_MS = 600_000;
|
|
10
|
-
const SHADCN_OUTPUT_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
11
|
-
const POSITIONAL_ARGUMENT_PREFIX = '-';
|
|
12
|
-
const PRESERVED_ENV_KEYS = ['PATH', 'Path', 'HOME', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'TEMP', 'TMP', 'SystemRoot', 'WINDIR'];
|
|
13
|
-
function resolveShadcnBinaryPath() {
|
|
14
|
-
const require = createRequire(import.meta.url);
|
|
15
|
-
const binaryPath = require.resolve('shadcn');
|
|
16
|
-
if (!existsSync(binaryPath)) {
|
|
17
|
-
throw new Error('Unable to resolve local shadcn binary from shadcn');
|
|
18
|
-
}
|
|
19
|
-
return binaryPath;
|
|
20
|
-
}
|
|
21
|
-
function assertShadcnArgs(args) {
|
|
22
|
-
if (args.length === 0) {
|
|
23
|
-
throw new Error('shadcn arguments are required');
|
|
24
|
-
}
|
|
25
|
-
if (args[0]?.startsWith(POSITIONAL_ARGUMENT_PREFIX)) {
|
|
26
|
-
throw new Error('shadcn command must not start with -');
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function createShadcnEnvironment(sourceEnv = process.env) {
|
|
30
|
-
const environment = {};
|
|
31
|
-
for (const key of PRESERVED_ENV_KEYS) {
|
|
32
|
-
const value = sourceEnv[key];
|
|
33
|
-
if (value !== undefined) {
|
|
34
|
-
environment[key] = value;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return environment;
|
|
38
|
-
}
|
|
39
|
-
function assertOutputLimit(currentSize, chunkSize) {
|
|
40
|
-
const nextSize = currentSize + chunkSize;
|
|
41
|
-
if (nextSize > SHADCN_OUTPUT_LIMIT_BYTES) {
|
|
42
|
-
throw new Error(`shadcn output exceeded ${SHADCN_OUTPUT_LIMIT_BYTES} bytes`);
|
|
43
|
-
}
|
|
44
|
-
return nextSize;
|
|
45
|
-
}
|
|
46
|
-
function terminateShadcnProcess(childProcess) {
|
|
47
|
-
if (childProcess.pid === undefined) {
|
|
48
|
-
childProcess.kill();
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (process.platform === 'win32') {
|
|
52
|
-
const taskkillPath = process.env.SystemRoot ? resolve(process.env.SystemRoot, 'System32', 'taskkill.exe') : 'taskkill.exe';
|
|
53
|
-
spawn(taskkillPath, ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore' });
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
process.kill(-childProcess.pid, 'SIGTERM');
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
childProcess.kill('SIGTERM');
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function defaultShadcnProcessRunner(invocation) {
|
|
64
|
-
return new Promise((resolveResult, reject) => {
|
|
65
|
-
const childProcess = spawn(invocation.executable, invocation.args, {
|
|
66
|
-
cwd: invocation.cwd,
|
|
67
|
-
detached: process.platform !== 'win32',
|
|
68
|
-
env: createShadcnEnvironment(),
|
|
69
|
-
shell: false
|
|
70
|
-
});
|
|
71
|
-
const timeout = setTimeout(() => {
|
|
72
|
-
terminateShadcnProcess(childProcess);
|
|
73
|
-
reject(new Error(`shadcn process timed out after ${SHADCN_PROCESS_TIMEOUT_MS}ms`));
|
|
74
|
-
}, SHADCN_PROCESS_TIMEOUT_MS);
|
|
75
|
-
const stdoutChunks = [];
|
|
76
|
-
const stderrChunks = [];
|
|
77
|
-
let stdoutSize = 0;
|
|
78
|
-
let stderrSize = 0;
|
|
79
|
-
childProcess.stdout.on('data', (chunk) => {
|
|
80
|
-
try {
|
|
81
|
-
stdoutSize = assertOutputLimit(stdoutSize, chunk.length);
|
|
82
|
-
stdoutChunks.push(chunk);
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
terminateShadcnProcess(childProcess);
|
|
86
|
-
reject(error);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
childProcess.stderr.on('data', (chunk) => {
|
|
90
|
-
try {
|
|
91
|
-
stderrSize = assertOutputLimit(stderrSize, chunk.length);
|
|
92
|
-
stderrChunks.push(chunk);
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
terminateShadcnProcess(childProcess);
|
|
96
|
-
reject(error);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
childProcess.on('error', (error) => {
|
|
100
|
-
clearTimeout(timeout);
|
|
101
|
-
reject(error);
|
|
102
|
-
});
|
|
103
|
-
childProcess.on('close', (exitCode) => {
|
|
104
|
-
clearTimeout(timeout);
|
|
105
|
-
resolveResult({
|
|
106
|
-
exitCode,
|
|
107
|
-
stdout: Buffer.concat(stdoutChunks).toString('utf8'),
|
|
108
|
-
stderr: Buffer.concat(stderrChunks).toString('utf8')
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
export function createShadcnInvocation(options) {
|
|
114
|
-
assertShadcnArgs(options.args);
|
|
115
|
-
return {
|
|
116
|
-
executable: SHADCN_EXECUTABLE,
|
|
117
|
-
args: [SHADCN_BINARY_PATH, ...options.args],
|
|
118
|
-
cwd: options.cwd ?? process.cwd(),
|
|
119
|
-
packageName: SHADCN_PACKAGE_NAME,
|
|
120
|
-
packageVersion: SHADCN_PACKAGE_VERSION
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
export async function executeShadcnInvocation(invocation, runner = defaultShadcnProcessRunner) {
|
|
124
|
-
return runner(invocation);
|
|
125
|
-
}
|
|
126
|
-
export const testInternals = {
|
|
127
|
-
createShadcnEnvironment
|
|
128
|
-
};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared `makeStubAdapter` helper for the 5 non-shipped IDEs (Trae, Cursor,
|
|
3
|
-
* Codex, Qoder, Tongyi Lingma).
|
|
4
|
-
*
|
|
5
|
-
* Each stub adapter:
|
|
6
|
-
* 1. Implements `SkillScopeAdapter` with `supported: false`.
|
|
7
|
-
* 2. In `applyScope`, ALWAYS writes the companion source-of-truth
|
|
8
|
-
* `.peaks/scope/<ide>-skills.json` first, then returns a NOT_SUPPORTED
|
|
9
|
-
* ApplyResult (the test contract asserts the source-of-truth is on disk
|
|
10
|
-
* even when the adapter can't apply it natively).
|
|
11
|
-
* 3. In `showScope`, reads from the companion source-of-truth file.
|
|
12
|
-
* 4. In `resetScope`, removes the companion source-of-truth file.
|
|
13
|
-
* 5. In `detect`, returns 0.0 (the stub does not actually probe).
|
|
14
|
-
*
|
|
15
|
-
* The TODO comment in each stub file points at the follow-up slice (025.2+).
|
|
16
|
-
*/
|
|
17
|
-
import type { SkillScopeAdapter } from '../types.js';
|
|
18
|
-
/**
|
|
19
|
-
* IDE-id -> companion source-of-truth shape. The companion file is a
|
|
20
|
-
* parallel record so the user can see "this is what would have applied"
|
|
21
|
-
* even when the IDE doesn't support a real implementation.
|
|
22
|
-
*/
|
|
23
|
-
export interface StubSourceOfTruth {
|
|
24
|
-
readonly ide: string;
|
|
25
|
-
readonly generatedAt: string;
|
|
26
|
-
readonly strict: boolean;
|
|
27
|
-
readonly allowlist: readonly string[];
|
|
28
|
-
readonly denylist: readonly string[];
|
|
29
|
-
readonly todoRef: string;
|
|
30
|
-
readonly notes: string;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* The factory: every stub is a thin wrapper around this function. The
|
|
34
|
-
* `applyScope` implementation ALWAYS writes the source-of-truth, then
|
|
35
|
-
* returns a NOT_SUPPORTED ApplyResult (NOT a thrown error — the contract
|
|
36
|
-
* for stub adapters is "return ok:false, notSupported:true" so the CLI
|
|
37
|
-
* can keep going and surface the error to the user).
|
|
38
|
-
*/
|
|
39
|
-
export declare function makeStubAdapter(ide: SkillScopeAdapter['ide'], todoRef: string, displayName: string): SkillScopeAdapter;
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared `makeStubAdapter` helper for the 5 non-shipped IDEs (Trae, Cursor,
|
|
3
|
-
* Codex, Qoder, Tongyi Lingma).
|
|
4
|
-
*
|
|
5
|
-
* Each stub adapter:
|
|
6
|
-
* 1. Implements `SkillScopeAdapter` with `supported: false`.
|
|
7
|
-
* 2. In `applyScope`, ALWAYS writes the companion source-of-truth
|
|
8
|
-
* `.peaks/scope/<ide>-skills.json` first, then returns a NOT_SUPPORTED
|
|
9
|
-
* ApplyResult (the test contract asserts the source-of-truth is on disk
|
|
10
|
-
* even when the adapter can't apply it natively).
|
|
11
|
-
* 3. In `showScope`, reads from the companion source-of-truth file.
|
|
12
|
-
* 4. In `resetScope`, removes the companion source-of-truth file.
|
|
13
|
-
* 5. In `detect`, returns 0.0 (the stub does not actually probe).
|
|
14
|
-
*
|
|
15
|
-
* The TODO comment in each stub file points at the follow-up slice (025.2+).
|
|
16
|
-
*/
|
|
17
|
-
import { existsSync } from 'node:fs';
|
|
18
|
-
import { readFile } from 'node:fs/promises';
|
|
19
|
-
import { ideCompanionFilePath, removeIfExists, scopeFilePath, writeJsonAtomic } from '../source-of-truth.js';
|
|
20
|
-
async function writeStubCompanion(ide, input, todoRef) {
|
|
21
|
-
const file = ideCompanionFilePath(input.projectRoot, ide);
|
|
22
|
-
const data = {
|
|
23
|
-
ide,
|
|
24
|
-
generatedAt: input.sourceConfig.generatedAt,
|
|
25
|
-
strict: input.strict,
|
|
26
|
-
allowlist: input.allowlist,
|
|
27
|
-
denylist: input.denylist,
|
|
28
|
-
todoRef,
|
|
29
|
-
notes: `Stub source-of-truth for ${ide}. The real config format has not yet been researched. ` +
|
|
30
|
-
`This file is written so the user's intent is captured and can be ported when ` +
|
|
31
|
-
`the follow-up slice (${todoRef}) lands.`,
|
|
32
|
-
};
|
|
33
|
-
await writeJsonAtomic(file, data);
|
|
34
|
-
return file;
|
|
35
|
-
}
|
|
36
|
-
async function readStubCompanion(ide, projectRoot) {
|
|
37
|
-
const file = ideCompanionFilePath(projectRoot, ide);
|
|
38
|
-
if (!existsSync(file))
|
|
39
|
-
return null;
|
|
40
|
-
try {
|
|
41
|
-
return JSON.parse(await readFile(file, 'utf8'));
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* The factory: every stub is a thin wrapper around this function. The
|
|
49
|
-
* `applyScope` implementation ALWAYS writes the source-of-truth, then
|
|
50
|
-
* returns a NOT_SUPPORTED ApplyResult (NOT a thrown error — the contract
|
|
51
|
-
* for stub adapters is "return ok:false, notSupported:true" so the CLI
|
|
52
|
-
* can keep going and surface the error to the user).
|
|
53
|
-
*/
|
|
54
|
-
export function makeStubAdapter(ide, todoRef, displayName) {
|
|
55
|
-
const ideStr = String(ide);
|
|
56
|
-
return {
|
|
57
|
-
ide,
|
|
58
|
-
supported: false,
|
|
59
|
-
async detect() {
|
|
60
|
-
// Stubs never "win" detection; they return 0.0 so the registry falls
|
|
61
|
-
// back to the shipped adapter (Claude Code).
|
|
62
|
-
return 0.0;
|
|
63
|
-
},
|
|
64
|
-
async applyScope(input) {
|
|
65
|
-
// 1. Always write the source-of-truth first.
|
|
66
|
-
const companion = await writeStubCompanion(ideStr, input, todoRef);
|
|
67
|
-
// 2. Always write the canonical .peaks/scope/skills.json too.
|
|
68
|
-
const canonical = scopeFilePath(input.projectRoot);
|
|
69
|
-
await writeJsonAtomic(canonical, input.sourceConfig);
|
|
70
|
-
// 3. Surface NOT_SUPPORTED with a clear, IDE-named message.
|
|
71
|
-
const message = `${displayName} (${ideStr}) config format not yet researched — ${todoRef} follow-up. ` +
|
|
72
|
-
`Source-of-truth written to ${companion}.`;
|
|
73
|
-
return {
|
|
74
|
-
ide,
|
|
75
|
-
ok: false,
|
|
76
|
-
writtenFiles: [companion, canonical],
|
|
77
|
-
usedShadowStub: false,
|
|
78
|
-
notSupported: true,
|
|
79
|
-
error: { code: 'NOT_SUPPORTED', message },
|
|
80
|
-
};
|
|
81
|
-
},
|
|
82
|
-
async showScope(projectRoot) {
|
|
83
|
-
const native = await readStubCompanion(ideStr, projectRoot);
|
|
84
|
-
return { source: null, native, ide };
|
|
85
|
-
},
|
|
86
|
-
async resetScope(input) {
|
|
87
|
-
const removed = [];
|
|
88
|
-
const companion = ideCompanionFilePath(input.projectRoot, ideStr);
|
|
89
|
-
if (await removeIfExists(companion))
|
|
90
|
-
removed.push(companion);
|
|
91
|
-
// Also remove the canonical source-of-truth on reset.
|
|
92
|
-
const canonical = scopeFilePath(input.projectRoot);
|
|
93
|
-
if (await removeIfExists(canonical))
|
|
94
|
-
removed.push(canonical);
|
|
95
|
-
return { ide, removedFiles: removed };
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `peaks skill scope` — Claude Code adapter (full impl, slice 025.1).
|
|
3
|
-
*
|
|
4
|
-
* Strategy (tech-doc-025 §3):
|
|
5
|
-
* 1. PRIMARY: write `.claude/settings.local.json` with
|
|
6
|
-
* `permissions.allow: ["Skill(name)", ...]` + `permissions.deny: [...]`.
|
|
7
|
-
* 2. FALLBACK (R1, `--shadow-fallback`): when the runtime probe determines
|
|
8
|
-
* Claude Code rejects `Skill(name)` in `permissions.deny`, write a
|
|
9
|
-
* shadow stub at `.claude/skills/<name>/SKILL.md` for each denylisted
|
|
10
|
-
* skill. Tagged with `_peaks_scope_disabled: true` (R6).
|
|
11
|
-
*
|
|
12
|
-
* Idempotency: dedupe the allow/deny arrays; shadow-stub writes skip
|
|
13
|
-
* when the marker is already present. AC11.
|
|
14
|
-
*/
|
|
15
|
-
import type { ApplyResult, ApplyScopeInput, ResetScopeInput, ResetScopeResult, ShowScopeResult, SkillScopeAdapter } from '../types.js';
|
|
16
|
-
/** Format the `Skill(name)` string Claude Code's permission system uses. */
|
|
17
|
-
export declare function skillRef(name: string): string;
|
|
18
|
-
interface ClaudePermissions {
|
|
19
|
-
readonly allow: string[];
|
|
20
|
-
readonly deny: string[];
|
|
21
|
-
}
|
|
22
|
-
interface ClaudeSettings {
|
|
23
|
-
readonly permissions: ClaudePermissions;
|
|
24
|
-
readonly [key: string]: unknown;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Map allowlist/denylist → permissions.allow/permissions.deny. Never sorts;
|
|
28
|
-
* preserves input order. Always dedupes.
|
|
29
|
-
*/
|
|
30
|
-
export declare function toPermissions(allowlist: readonly string[], denylist: readonly string[]): ClaudeSettings;
|
|
31
|
-
/**
|
|
32
|
-
* Strip any peaks-* name from the denylist (G6 hard constraint). Returns
|
|
33
|
-
* the cleaned denylist + the list of stripped names for the audit log.
|
|
34
|
-
*/
|
|
35
|
-
export declare function stripPeaksFromDenylist(denylist: readonly string[]): {
|
|
36
|
-
readonly cleaned: readonly string[];
|
|
37
|
-
readonly stripped: readonly string[];
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* Runtime probe for whether Claude Code supports `Skill(name)` syntax in
|
|
41
|
-
* `permissions.deny` (R1). For slice 025.1 we return `unknown` and let
|
|
42
|
-
* the caller decide. Replace this with a real check when Claude Code's
|
|
43
|
-
* `permissions.deny` schema is documented.
|
|
44
|
-
*/
|
|
45
|
-
export declare function probeSkillDenySupport(): Promise<'support-allow-and-deny' | 'support-allow-only' | 'unknown'>;
|
|
46
|
-
export declare class ClaudeCodeSkillScope implements SkillScopeAdapter {
|
|
47
|
-
readonly ide: "claude-code";
|
|
48
|
-
readonly supported = true;
|
|
49
|
-
constructor(_opts?: {
|
|
50
|
-
readonly projectRoot?: string;
|
|
51
|
-
});
|
|
52
|
-
/** detect(): returns 1.0 when the project root has a .claude/ dir. */
|
|
53
|
-
detect(projectRoot: string): Promise<number>;
|
|
54
|
-
applyScope(input: ApplyScopeInput): Promise<ApplyResult>;
|
|
55
|
-
showScope(projectRoot: string): Promise<ShowScopeResult>;
|
|
56
|
-
resetScope(input: ResetScopeInput): Promise<ResetScopeResult>;
|
|
57
|
-
}
|
|
58
|
-
export declare const CLAUDE_CODE_SKILL_SCOPE: SkillScopeAdapter;
|
|
59
|
-
export {};
|
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `peaks skill scope` — Claude Code adapter (full impl, slice 025.1).
|
|
3
|
-
*
|
|
4
|
-
* Strategy (tech-doc-025 §3):
|
|
5
|
-
* 1. PRIMARY: write `.claude/settings.local.json` with
|
|
6
|
-
* `permissions.allow: ["Skill(name)", ...]` + `permissions.deny: [...]`.
|
|
7
|
-
* 2. FALLBACK (R1, `--shadow-fallback`): when the runtime probe determines
|
|
8
|
-
* Claude Code rejects `Skill(name)` in `permissions.deny`, write a
|
|
9
|
-
* shadow stub at `.claude/skills/<name>/SKILL.md` for each denylisted
|
|
10
|
-
* skill. Tagged with `_peaks_scope_disabled: true` (R6).
|
|
11
|
-
*
|
|
12
|
-
* Idempotency: dedupe the allow/deny arrays; shadow-stub writes skip
|
|
13
|
-
* when the marker is already present. AC11.
|
|
14
|
-
*/
|
|
15
|
-
import { existsSync } from 'node:fs';
|
|
16
|
-
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
17
|
-
import { dirname, join } from 'node:path';
|
|
18
|
-
/** Adapter id. */
|
|
19
|
-
const IDE_ID = 'claude-code';
|
|
20
|
-
/** Format the `Skill(name)` string Claude Code's permission system uses. */
|
|
21
|
-
export function skillRef(name) {
|
|
22
|
-
return `Skill(${name})`;
|
|
23
|
-
}
|
|
24
|
-
/** Dedupe a list preserving the first-seen order. */
|
|
25
|
-
function dedupe(list) {
|
|
26
|
-
const seen = new Set();
|
|
27
|
-
const out = [];
|
|
28
|
-
for (const item of list) {
|
|
29
|
-
if (!seen.has(item)) {
|
|
30
|
-
seen.add(item);
|
|
31
|
-
out.push(item);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return out;
|
|
35
|
-
}
|
|
36
|
-
const EMPTY_SETTINGS = { permissions: { allow: [], deny: [] } };
|
|
37
|
-
/**
|
|
38
|
-
* Read the existing `.claude/settings.local.json` (returns empty settings
|
|
39
|
-
* if the file does not exist). On parse failure returns empty settings
|
|
40
|
-
* and a warning rather than throwing — the user can still write fresh
|
|
41
|
-
* settings.
|
|
42
|
-
*/
|
|
43
|
-
async function readSettingsLocal(projectRoot) {
|
|
44
|
-
const file = join(projectRoot, '.claude', 'settings.local.json');
|
|
45
|
-
if (!existsSync(file))
|
|
46
|
-
return { settings: EMPTY_SETTINGS, existed: false, malformed: false };
|
|
47
|
-
try {
|
|
48
|
-
const raw = await readFile(file, 'utf8');
|
|
49
|
-
const parsed = JSON.parse(raw);
|
|
50
|
-
if (parsed === null || typeof parsed !== 'object') {
|
|
51
|
-
return { settings: EMPTY_SETTINGS, existed: true, malformed: true };
|
|
52
|
-
}
|
|
53
|
-
const obj = parsed;
|
|
54
|
-
const permsRaw = obj.permissions;
|
|
55
|
-
const perms = permsRaw !== null && typeof permsRaw === 'object'
|
|
56
|
-
? {
|
|
57
|
-
allow: Array.isArray(permsRaw.allow)
|
|
58
|
-
? permsRaw.allow
|
|
59
|
-
: [],
|
|
60
|
-
deny: Array.isArray(permsRaw.deny)
|
|
61
|
-
? permsRaw.deny
|
|
62
|
-
: [],
|
|
63
|
-
}
|
|
64
|
-
: { allow: [], deny: [] };
|
|
65
|
-
return { settings: { ...obj, permissions: perms }, existed: true, malformed: false };
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return { settings: EMPTY_SETTINGS, existed: true, malformed: true };
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Write the JSON file atomically via `.peaks-tmp` + `rename`. Removes the
|
|
73
|
-
* temp file on partial failure.
|
|
74
|
-
*/
|
|
75
|
-
async function writeJsonAtomic(file, data) {
|
|
76
|
-
await mkdir(dirname(file), { recursive: true });
|
|
77
|
-
const tmp = `${file}.peaks-tmp`;
|
|
78
|
-
try {
|
|
79
|
-
await writeFile(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
80
|
-
await rename(tmp, file);
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
if (existsSync(tmp)) {
|
|
84
|
-
try {
|
|
85
|
-
await rm(tmp, { force: true });
|
|
86
|
-
}
|
|
87
|
-
catch { /* best-effort */ }
|
|
88
|
-
}
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Map allowlist/denylist → permissions.allow/permissions.deny. Never sorts;
|
|
94
|
-
* preserves input order. Always dedupes.
|
|
95
|
-
*/
|
|
96
|
-
export function toPermissions(allowlist, denylist) {
|
|
97
|
-
return {
|
|
98
|
-
permissions: {
|
|
99
|
-
allow: dedupe(allowlist.map(skillRef)),
|
|
100
|
-
deny: dedupe(denylist.map(skillRef)),
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Strip any peaks-* name from the denylist (G6 hard constraint). Returns
|
|
106
|
-
* the cleaned denylist + the list of stripped names for the audit log.
|
|
107
|
-
*/
|
|
108
|
-
export function stripPeaksFromDenylist(denylist) {
|
|
109
|
-
const cleaned = [];
|
|
110
|
-
const stripped = [];
|
|
111
|
-
for (const name of denylist) {
|
|
112
|
-
if (name.startsWith('peaks-')) {
|
|
113
|
-
stripped.push(name);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
cleaned.push(name);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return { cleaned, stripped };
|
|
120
|
-
}
|
|
121
|
-
/** Render the shadow-stub SKILL.md body (R6 marker). */
|
|
122
|
-
function shadowStubBody(name) {
|
|
123
|
-
return `---
|
|
124
|
-
name: ${name}
|
|
125
|
-
description: _peaks_scope_disabled
|
|
126
|
-
_peaks_scope_disabled: true
|
|
127
|
-
---
|
|
128
|
-
# Disabled by \`peaks skill scope --apply\`
|
|
129
|
-
|
|
130
|
-
This skill is shadowed because the project has marked it as out of scope.
|
|
131
|
-
To restore, run \`peaks skill scope --reset\` or edit \`.peaks/scope/skills.json\`.
|
|
132
|
-
`;
|
|
133
|
-
}
|
|
134
|
-
async function writeShadowStub(projectRoot, name) {
|
|
135
|
-
const file = join(projectRoot, '.claude', 'skills', name, 'SKILL.md');
|
|
136
|
-
await mkdir(dirname(file), { recursive: true });
|
|
137
|
-
// Skip if already has the marker
|
|
138
|
-
if (existsSync(file)) {
|
|
139
|
-
try {
|
|
140
|
-
const existing = await readFile(file, 'utf8');
|
|
141
|
-
if (existing.includes('_peaks_scope_disabled: true')) {
|
|
142
|
-
return file;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
catch { /* fall through and overwrite */ }
|
|
146
|
-
}
|
|
147
|
-
await writeFile(file, shadowStubBody(name), 'utf8');
|
|
148
|
-
return file;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Runtime probe for whether Claude Code supports `Skill(name)` syntax in
|
|
152
|
-
* `permissions.deny` (R1). For slice 025.1 we return `unknown` and let
|
|
153
|
-
* the caller decide. Replace this with a real check when Claude Code's
|
|
154
|
-
* `permissions.deny` schema is documented.
|
|
155
|
-
*/
|
|
156
|
-
export async function probeSkillDenySupport() {
|
|
157
|
-
return 'unknown';
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Decision: should the denylist use shadow stubs instead of `permissions.deny`?
|
|
161
|
-
* Returns true when:
|
|
162
|
-
* - The caller passes `shadowFallback: true`, OR
|
|
163
|
-
* - The runtime probe returns `support-allow-only` / `unknown`.
|
|
164
|
-
*/
|
|
165
|
-
async function shouldUseShadowStubs(input) {
|
|
166
|
-
if (input.shadowFallback)
|
|
167
|
-
return true;
|
|
168
|
-
const probe = await probeSkillDenySupport();
|
|
169
|
-
return probe !== 'support-allow-and-deny';
|
|
170
|
-
}
|
|
171
|
-
/** Strip the peaks-* shadow stubs written by a previous apply. */
|
|
172
|
-
function shadowStubDir(projectRoot, name) {
|
|
173
|
-
return join(projectRoot, '.claude', 'skills', name, 'SKILL.md');
|
|
174
|
-
}
|
|
175
|
-
async function removeShadowStubIfPresent(projectRoot, name) {
|
|
176
|
-
const file = shadowStubDir(projectRoot, name);
|
|
177
|
-
if (!existsSync(file))
|
|
178
|
-
return false;
|
|
179
|
-
try {
|
|
180
|
-
const raw = await readFile(file, 'utf8');
|
|
181
|
-
if (!raw.includes('_peaks_scope_disabled: true'))
|
|
182
|
-
return false;
|
|
183
|
-
await rm(file, { force: true });
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
export class ClaudeCodeSkillScope {
|
|
191
|
-
ide = IDE_ID;
|
|
192
|
-
supported = true;
|
|
193
|
-
constructor(_opts) {
|
|
194
|
-
// projectRoot is supplied on every method call; the class is stateless.
|
|
195
|
-
void _opts;
|
|
196
|
-
}
|
|
197
|
-
/** detect(): returns 1.0 when the project root has a .claude/ dir. */
|
|
198
|
-
async detect(projectRoot) {
|
|
199
|
-
return existsSync(join(projectRoot, '.claude')) ? 1.0 : 0.5;
|
|
200
|
-
}
|
|
201
|
-
async applyScope(input) {
|
|
202
|
-
const written = [];
|
|
203
|
-
const removed = [];
|
|
204
|
-
// G6: strip peaks-* from the denylist.
|
|
205
|
-
const { cleaned: cleanedDeny, stripped } = stripPeaksFromDenylist(input.denylist);
|
|
206
|
-
if (stripped.length > 0) {
|
|
207
|
-
removed.push(...stripped);
|
|
208
|
-
}
|
|
209
|
-
const useShadows = await shouldUseShadowStubs(input);
|
|
210
|
-
// 1. Write (or skip) settings.local.json
|
|
211
|
-
const { settings: existing } = await readSettingsLocal(input.projectRoot);
|
|
212
|
-
const next = toPermissions(input.allowlist, cleanedDeny);
|
|
213
|
-
// Preserve existing non-permissions fields (theme, env, etc.).
|
|
214
|
-
const preserved = { ...existing };
|
|
215
|
-
delete preserved.permissions;
|
|
216
|
-
// Merge with the user's pre-existing allow/deny entries (deduped).
|
|
217
|
-
const existingAllow = (existing.permissions.allow ?? []);
|
|
218
|
-
const existingDeny = (existing.permissions.deny ?? []);
|
|
219
|
-
const merged = {
|
|
220
|
-
...preserved,
|
|
221
|
-
permissions: {
|
|
222
|
-
allow: dedupe([...existingAllow, ...next.permissions.allow]),
|
|
223
|
-
deny: dedupe([...existingDeny, ...next.permissions.deny]),
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
if (input.simulateWriteFailure) {
|
|
227
|
-
throw new Error('simulated write failure (settings.local.json)');
|
|
228
|
-
}
|
|
229
|
-
const settingsFile = join(input.projectRoot, '.claude', 'settings.local.json');
|
|
230
|
-
await writeJsonAtomic(settingsFile, merged);
|
|
231
|
-
written.push(settingsFile);
|
|
232
|
-
// 2. Optionally write shadow stubs for the (stripped, cleaned) denylist
|
|
233
|
-
let usedShadowStub = false;
|
|
234
|
-
if (useShadows) {
|
|
235
|
-
usedShadowStub = true;
|
|
236
|
-
for (const name of cleanedDeny) {
|
|
237
|
-
const stub = await writeShadowStub(input.projectRoot, name);
|
|
238
|
-
written.push(stub);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
ide: this.ide,
|
|
243
|
-
ok: true,
|
|
244
|
-
writtenFiles: written,
|
|
245
|
-
usedShadowStub,
|
|
246
|
-
notSupported: false,
|
|
247
|
-
strippedFromDenylist: stripped,
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
async showScope(projectRoot) {
|
|
251
|
-
const settingsFile = join(projectRoot, '.claude', 'settings.local.json');
|
|
252
|
-
let native = null;
|
|
253
|
-
if (existsSync(settingsFile)) {
|
|
254
|
-
try {
|
|
255
|
-
native = JSON.parse(await readFile(settingsFile, 'utf8'));
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
native = null;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return { source: null, native, ide: this.ide };
|
|
262
|
-
}
|
|
263
|
-
async resetScope(input) {
|
|
264
|
-
const removed = [];
|
|
265
|
-
const settingsFile = join(input.projectRoot, '.claude', 'settings.local.json');
|
|
266
|
-
if (existsSync(settingsFile)) {
|
|
267
|
-
// Only remove if it has a permissions.allow/deny field shaped by us,
|
|
268
|
-
// otherwise leave the user's hand-curated file alone.
|
|
269
|
-
try {
|
|
270
|
-
const raw = await readFile(settingsFile, 'utf8');
|
|
271
|
-
const parsed = JSON.parse(raw);
|
|
272
|
-
if (parsed !== null &&
|
|
273
|
-
typeof parsed === 'object' &&
|
|
274
|
-
parsed.permissions !== undefined) {
|
|
275
|
-
await rm(settingsFile, { force: true });
|
|
276
|
-
removed.push(settingsFile);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
catch {
|
|
280
|
-
// best-effort
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
// Also remove shadow stubs (we don't know which skills are stubbed, so
|
|
284
|
-
// we don't blindly walk .claude/skills; the caller can re-run detect +
|
|
285
|
-
// reset if they need a full sweep). For the explicit case we know
|
|
286
|
-
// about, we strip any stub whose marker is present.
|
|
287
|
-
const skillsDir = join(input.projectRoot, '.claude', 'skills');
|
|
288
|
-
if (existsSync(skillsDir)) {
|
|
289
|
-
// Best-effort scan: only act when the file is unmistakably a stub.
|
|
290
|
-
const { readdirSync } = await import('node:fs');
|
|
291
|
-
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
292
|
-
for (const entry of entries) {
|
|
293
|
-
if (!entry.isDirectory())
|
|
294
|
-
continue;
|
|
295
|
-
const removedStub = await removeShadowStubIfPresent(input.projectRoot, entry.name);
|
|
296
|
-
if (removedStub) {
|
|
297
|
-
removed.push(join(input.projectRoot, '.claude', 'skills', entry.name, 'SKILL.md'));
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return { ide: this.ide, removedFiles: removed };
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
export const CLAUDE_CODE_SKILL_SCOPE = new ClaudeCodeSkillScope();
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// TODO(slice-025.4-codex): research Codex's per-project skill scoping
|
|
2
|
-
// config format. Codex uses `.codex/` for project-local config.
|
|
3
|
-
//
|
|
4
|
-
// Until the real format is known, this stub:
|
|
5
|
-
// 1. Writes `.peaks/scope/codex-skills.json` (source-of-truth) on every
|
|
6
|
-
// `applyScope` so the user's intent is captured on disk.
|
|
7
|
-
// 2. Returns NOT_SUPPORTED with a clear message pointing at this slice.
|
|
8
|
-
//
|
|
9
|
-
// When implementing, replace the makeStubAdapter call with the real
|
|
10
|
-
// CodexSkillScope class.
|
|
11
|
-
import { makeStubAdapter } from './_stub-helper.js';
|
|
12
|
-
export const CODEX_SKILL_SCOPE = makeStubAdapter('codex', 'slice-025.4-codex', 'Codex');
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// TODO(slice-025.3-cursor): research Cursor's per-project skill scoping
|
|
2
|
-
// config format. Cursor uses `.cursor/` for project-local config; the
|
|
3
|
-
// skill-scope hook may live there or in `.cursor/rules/`.
|
|
4
|
-
//
|
|
5
|
-
// Until the real format is known, this stub:
|
|
6
|
-
// 1. Writes `.peaks/scope/cursor-skills.json` (source-of-truth) on every
|
|
7
|
-
// `applyScope` so the user's intent is captured on disk.
|
|
8
|
-
// 2. Returns NOT_SUPPORTED with a clear message pointing at this slice.
|
|
9
|
-
//
|
|
10
|
-
// When implementing, replace the makeStubAdapter call with the real
|
|
11
|
-
// CursorSkillScope class.
|
|
12
|
-
import { makeStubAdapter } from './_stub-helper.js';
|
|
13
|
-
export const CURSOR_SKILL_SCOPE = makeStubAdapter('cursor', 'slice-025.3-cursor', 'Cursor');
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// TODO(slice-025.5-qoder): research Qoder's per-project skill scoping
|
|
2
|
-
// config format. Qoder is an AI IDE by Alibaba; per-project config dir
|
|
3
|
-
// is unconfirmed at slice 025.1.
|
|
4
|
-
//
|
|
5
|
-
// Until the real format is known, this stub:
|
|
6
|
-
// 1. Writes `.peaks/scope/qoder-skills.json` (source-of-truth) on every
|
|
7
|
-
// `applyScope` so the user's intent is captured on disk.
|
|
8
|
-
// 2. Returns NOT_SUPPORTED with a clear message pointing at this slice.
|
|
9
|
-
//
|
|
10
|
-
// When implementing, replace the makeStubAdapter call with the real
|
|
11
|
-
// QoderSkillScope class.
|
|
12
|
-
import { makeStubAdapter } from './_stub-helper.js';
|
|
13
|
-
export const QODER_SKILL_SCOPE = makeStubAdapter('qoder', 'slice-025.5-qoder', 'Qoder');
|