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,82 @@
|
|
|
1
|
+
# Step 0.55 — 1.x → 2.0 detection reference
|
|
2
|
+
|
|
3
|
+
> Body of `### Peaks-Cli Step 0.55`. Slice: 2026-06-12-solo-step-0-55-1x-detection.
|
|
4
|
+
|
|
5
|
+
## Why this step exists
|
|
6
|
+
|
|
7
|
+
The peaks-cli 1.x → 2.0 closeout ships:
|
|
8
|
+
|
|
9
|
+
1. A postinstall that auto-detects 1.x state and dispatches the upgrade (slice 1, commit `b6e34e6`).
|
|
10
|
+
2. A standards-migrate path that thins `.claude/rules/**/*.md` and scaffolds `.peaks/standards/` (slice 2, commit `33dd392`).
|
|
11
|
+
3. **THIS STEP** — a peaks-solo startup sequence probe that detects 1.x state when the user invokes `/peaks-solo` directly in a 1.x consumer project, and prompts the user to upgrade.
|
|
12
|
+
|
|
13
|
+
The 1.x user experience is: "I just typed /peaks-solo. Why is it not working in 2.0 mode?" Step 0.55 catches this case and surfaces an `AskUserQuestion` with a one-click upgrade.
|
|
14
|
+
|
|
15
|
+
## Detection algorithm
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 1. Read-only probe via the umbrella CLI
|
|
19
|
+
peaks upgrade --detect-1x --project <root> --json
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The CLI returns:
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"ok": true,
|
|
26
|
+
"command": "upgrade.detect-1x",
|
|
27
|
+
"data": {
|
|
28
|
+
"isOneX": true,
|
|
29
|
+
"signals": [
|
|
30
|
+
"<path> has schema_version 1.0.0, expected '2.0.0'",
|
|
31
|
+
"..."
|
|
32
|
+
],
|
|
33
|
+
"projectRoot": "/path/to/project",
|
|
34
|
+
"configPath": null
|
|
35
|
+
},
|
|
36
|
+
"warnings": [],
|
|
37
|
+
"nextActions": [
|
|
38
|
+
"Detected 1.x state. peaks-solo Step 0.55 should present an AskUserQuestion to invoke `peaks upgrade --to 2.0 --auto --project /path/to/project`."
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The detection logic mirrors `scripts/install-skills.mjs:detect1xProjectState` (canonical implementation) — it walks up to find `.peaks/_runtime/`, then sniffs:
|
|
44
|
+
|
|
45
|
+
1. `~/.peaks/config.json` for `version: 1.x`
|
|
46
|
+
2. `<projectRoot>/.claude/rules/common/dev-preference.md` for "peaks progress"
|
|
47
|
+
3. `<projectRoot>/.peaks/preferences.json` for missing or non-`2.0.0` `schema_version`
|
|
48
|
+
|
|
49
|
+
The TS mirror in `src/services/upgrade/1x-detector-service.ts` is the canonical entrypoint for the skill; the postinstall `.mjs` version is the canonical entrypoint for the npm-install flow. The two implementations MUST stay in parity (a parity test is in the slice's test suite).
|
|
50
|
+
|
|
51
|
+
## AskUserQuestion (only when `isOneX: true`)
|
|
52
|
+
|
|
53
|
+
| Option | What it does |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Run `peaks upgrade --to 2.0 --auto --project <root>` (Recommended) | Invokes the umbrella. The user sees the 6 sub-step results in the terminal. After the upgrade, re-run peaks-solo with the standing 2.0 layout. Persist `autoUpgradePrompt: opt-in` to `.peaks/preferences.json`. |
|
|
56
|
+
| Skip for this session | Continue with the standing 1.x layout. Persist `autoUpgradePrompt: skip-this-session` to `.peaks/preferences.json`. The next time the user invokes peaks-solo in this project, the question re-asks. |
|
|
57
|
+
| Never ask again for this project | Persist `autoUpgradePrompt: skip-forever` to `.peaks/preferences.json`. Step 0.55 becomes a no-op for this project from now on. The user can re-enable later by removing the `autoUpgradePrompt` key from preferences.json. |
|
|
58
|
+
|
|
59
|
+
## Persistence contract
|
|
60
|
+
|
|
61
|
+
The decision is persisted via `peaks preferences set --project <root> --key autoUpgradePrompt --value <opt-in|skip-this-session|skip-forever> --apply`. Subsequent Step 0.55 invocations read the value first:
|
|
62
|
+
|
|
63
|
+
- If `opt-in` (and Step 0.55 is the first invocation in the session), the user has already opted in; auto-run the umbrella without re-asking.
|
|
64
|
+
- If `skip-this-session`, the user already said no this session; skip without re-asking (but re-ask next session).
|
|
65
|
+
- If `skip-forever`, the user said no permanently; skip without re-asking.
|
|
66
|
+
- If the key is absent, present the AskUserQuestion.
|
|
67
|
+
|
|
68
|
+
## What is NOT in this step
|
|
69
|
+
|
|
70
|
+
- The auto-upgrade execution itself: the umbrella is invoked via the AskUserQuestion's recommended option. The umbrella's behavior (6 sub-commands + write-upgrade-record) is documented in the umbrella's own help text.
|
|
71
|
+
- The postinstall auto-dispatch: that's `scripts/install-skills.mjs:autoUpgrade1xProjectIfPresent`, which fires fire-and-forget on `npm i -g peaks-cli@2.0`. Step 0.55 is the user-invoked path.
|
|
72
|
+
- Re-authoring the 1.x detector heuristics: the implementation is a 1:1 mirror of the canonical `.mjs` version. Drift is prevented by the parity test.
|
|
73
|
+
|
|
74
|
+
## How this integrates with the rest of the workflow
|
|
75
|
+
|
|
76
|
+
- Step 0.5 (OpenSpec opt-in) — runs first if `openspec/` is missing.
|
|
77
|
+
- Step 0 (anchor) — runs always.
|
|
78
|
+
- Step 0.7 (resume detection) — runs after Step 0.
|
|
79
|
+
- **Step 0.55 (1.x detection) — runs after Step 0.7, only when the project is not on a 2.0 layout.**
|
|
80
|
+
- Step 1 (mode selection) — runs after Step 0.55.
|
|
81
|
+
|
|
82
|
+
The 1.x detection is intentionally placed AFTER Step 0.7 because the user might already have a 1.x-converted in-flight slice; the resume flow takes precedence over the upgrade prompt.
|
|
@@ -102,6 +102,15 @@ ls .peaks/<id>/ui/design-draft.md 2>&1
|
|
|
102
102
|
|
|
103
103
|
The CLI gate (`peaks request transition --state qa-handoff`) is the authoritative check; running this `ls` first lets you produce missing files before the CLI rejects the transition.
|
|
104
104
|
|
|
105
|
+
| Request type | Required RD evidence (under `.peaks/<id>/`) |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `feature` / `refactor` | `rd/tech-doc.md` + `rd/code-review.md` + `rd/security-review.md` + `rd/perf-baseline.md` + `qa/test-cases/<rid>.md` (qa/test-cases pre-drafted by the 4th sub-agent in peaks-rd's parallel fan-out — slice 004) |
|
|
108
|
+
| `bugfix` | `rd/bug-analysis.md` + `rd/code-review.md` + `rd/security-review.md` + `qa/test-cases/<rid>.md` (rd/perf-baseline.md only when the bug is performance-shaped) |
|
|
109
|
+
| `config` | `rd/security-review.md` |
|
|
110
|
+
| `docs` / `chore` | (no extra evidence required) |
|
|
111
|
+
|
|
112
|
+
Always required (in addition to the type-specific row): `ls .peaks/<id>/rd/requests/<rid>.md`. Missing any required file → DO NOT attempt the qa-handoff transition; CLI will reject with PREREQUISITES_MISSING.
|
|
113
|
+
|
|
105
114
|
```bash
|
|
106
115
|
# Always required
|
|
107
116
|
ls .peaks/<id>/rd/requests/<rid>.md
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { createShadcnInvocation, executeShadcnInvocation } from '../../services/shadcn/shadcn-service.js';
|
|
2
|
-
import { fail } from '../../shared/result.js';
|
|
3
|
-
import { getErrorMessage, printResult, redactSensitiveErrorMessage } from '../cli-helpers.js';
|
|
4
|
-
function printShadcnFailure(io, error, exitCode = 1) {
|
|
5
|
-
printResult(io, fail('shadcn', 'SHADCN_COMMAND_FAILED', redactSensitiveErrorMessage(getErrorMessage(error)), {}, ['Check the shadcn command arguments before retrying']), false);
|
|
6
|
-
process.exitCode = exitCode;
|
|
7
|
-
}
|
|
8
|
-
async function runShadcnCommand(io, args) {
|
|
9
|
-
try {
|
|
10
|
-
const invocation = createShadcnInvocation({ args });
|
|
11
|
-
const result = await executeShadcnInvocation(invocation);
|
|
12
|
-
const didFail = result.exitCode !== null && result.exitCode !== 0;
|
|
13
|
-
if (result.stdout.length > 0) {
|
|
14
|
-
io.stdout((didFail ? redactSensitiveErrorMessage(result.stdout) : result.stdout).trimEnd());
|
|
15
|
-
}
|
|
16
|
-
if (result.stderr.length > 0) {
|
|
17
|
-
io.stderr((didFail ? redactSensitiveErrorMessage(result.stderr) : result.stderr).trimEnd());
|
|
18
|
-
}
|
|
19
|
-
if (didFail) {
|
|
20
|
-
process.exitCode = result.exitCode;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
printShadcnFailure(io, error);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
export function registerShadcnCommands(program, io) {
|
|
28
|
-
program
|
|
29
|
-
.command('shadcn')
|
|
30
|
-
.description('Run the pinned shadcn CLI bundled with Peaks')
|
|
31
|
-
.allowUnknownOption(true)
|
|
32
|
-
.helpOption(false)
|
|
33
|
-
.argument('<args...>', 'arguments forwarded to shadcn')
|
|
34
|
-
.action((args) => runShadcnCommand(io, args));
|
|
35
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `peaks skill context-stats` — R003.2
|
|
3
|
-
*
|
|
4
|
-
* Reports the per-project skill context footprint for the LLM:
|
|
5
|
-
* - Total bytes of allowed skills (full SKILL.md)
|
|
6
|
-
* - Total bytes of denied skills (shadow stubs in the project-local mirror)
|
|
7
|
-
* - Estimated token counts (chars/4 for full skills, bytes*0.25 for stubs)
|
|
8
|
-
* - Shadow-stub reduction percentage vs. the original full SKILL.md bytes
|
|
9
|
-
*
|
|
10
|
-
* If no scope is applied (no `.peaks/scope/skills.json`), returns the
|
|
11
|
-
* "no-scope" branch with a recommended command.
|
|
12
|
-
*/
|
|
13
|
-
import { type ResultEnvelope } from '../../shared/result.js';
|
|
14
|
-
export interface RunContextStatsInput {
|
|
15
|
-
readonly projectRoot: string;
|
|
16
|
-
readonly json: boolean;
|
|
17
|
-
/** Average bytes-per-skill estimate for denied skills without a real SKILL.md (default 7000). */
|
|
18
|
-
readonly estimatedDeniedOriginalBytes?: number;
|
|
19
|
-
}
|
|
20
|
-
export interface ContextStatsData {
|
|
21
|
-
readonly scope: unknown;
|
|
22
|
-
readonly totals: {
|
|
23
|
-
readonly allowedCount: number;
|
|
24
|
-
readonly deniedCount: number;
|
|
25
|
-
readonly allowedBytes: number;
|
|
26
|
-
readonly stubBytes: number;
|
|
27
|
-
readonly originalDeniedBytes: number;
|
|
28
|
-
readonly shadowReductionPct: number;
|
|
29
|
-
readonly totalBytes: number;
|
|
30
|
-
};
|
|
31
|
-
readonly estimatedTokens: {
|
|
32
|
-
readonly allowed: number;
|
|
33
|
-
readonly denied: number;
|
|
34
|
-
readonly total: number;
|
|
35
|
-
};
|
|
36
|
-
readonly message?: string;
|
|
37
|
-
readonly recommendedCommand?: string;
|
|
38
|
-
readonly human?: string;
|
|
39
|
-
}
|
|
40
|
-
export declare function runContextStats(input: RunContextStatsInput): Promise<ResultEnvelope<ContextStatsData>>;
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `peaks skill context-stats` — R003.2
|
|
3
|
-
*
|
|
4
|
-
* Reports the per-project skill context footprint for the LLM:
|
|
5
|
-
* - Total bytes of allowed skills (full SKILL.md)
|
|
6
|
-
* - Total bytes of denied skills (shadow stubs in the project-local mirror)
|
|
7
|
-
* - Estimated token counts (chars/4 for full skills, bytes*0.25 for stubs)
|
|
8
|
-
* - Shadow-stub reduction percentage vs. the original full SKILL.md bytes
|
|
9
|
-
*
|
|
10
|
-
* If no scope is applied (no `.peaks/scope/skills.json`), returns the
|
|
11
|
-
* "no-scope" branch with a recommended command.
|
|
12
|
-
*/
|
|
13
|
-
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
14
|
-
import { join } from 'node:path';
|
|
15
|
-
import { ok, fail } from '../../shared/result.js';
|
|
16
|
-
export async function runContextStats(input) {
|
|
17
|
-
const projectRoot = input.projectRoot;
|
|
18
|
-
const scopePath = join(projectRoot, '.peaks', 'scope', 'skills.json');
|
|
19
|
-
if (!existsSync(scopePath)) {
|
|
20
|
-
const noScopeData = {
|
|
21
|
-
scope: null,
|
|
22
|
-
totals: {
|
|
23
|
-
allowedCount: 0,
|
|
24
|
-
deniedCount: 0,
|
|
25
|
-
allowedBytes: 0,
|
|
26
|
-
stubBytes: 0,
|
|
27
|
-
originalDeniedBytes: 0,
|
|
28
|
-
shadowReductionPct: 0,
|
|
29
|
-
totalBytes: 0,
|
|
30
|
-
},
|
|
31
|
-
estimatedTokens: { allowed: 0, denied: 0, total: 0 },
|
|
32
|
-
message: 'No scope applied. Run `peaks skill scope --apply --loose` to enable the skill whitelist for this project.',
|
|
33
|
-
recommendedCommand: 'peaks skill scope --apply --loose',
|
|
34
|
-
};
|
|
35
|
-
return fail('skill.context-stats', 'NO_SCOPE', 'No scope applied to this project.', noScopeData);
|
|
36
|
-
}
|
|
37
|
-
const raw = JSON.parse(readFileSync(scopePath, 'utf8'));
|
|
38
|
-
const allowlist = raw.allowlist;
|
|
39
|
-
const denied = [];
|
|
40
|
-
// Walk .claude/skills/ to find shadow stubs (denied skills not in allowlist).
|
|
41
|
-
const skillsMirror = join(projectRoot, '.claude', 'skills');
|
|
42
|
-
let stubBytes = 0;
|
|
43
|
-
let originalDeniedBytes = 0;
|
|
44
|
-
const estimatedDeniedOriginalBytes = input.estimatedDeniedOriginalBytes ?? 7000;
|
|
45
|
-
if (existsSync(skillsMirror)) {
|
|
46
|
-
for (const entry of readdirSync(skillsMirror)) {
|
|
47
|
-
const skillMd = join(skillsMirror, entry, 'SKILL.md');
|
|
48
|
-
if (!existsSync(skillMd))
|
|
49
|
-
continue;
|
|
50
|
-
if (allowlist.includes(entry))
|
|
51
|
-
continue; // allowed skills are NOT in the mirror
|
|
52
|
-
denied.push(entry);
|
|
53
|
-
const stat = statSync(skillMd);
|
|
54
|
-
stubBytes += stat.size;
|
|
55
|
-
// Heuristic: a denied skill would have loaded its full body (~7000 bytes) without shadow-fallback.
|
|
56
|
-
// We use this as the "original" to compute the reduction.
|
|
57
|
-
originalDeniedBytes += estimatedDeniedOriginalBytes;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// For allowed skills, compute the sum of their original SKILL.md bytes from the global catalog.
|
|
61
|
-
// We don't know the global catalog path here; estimate at 364 KB / 44 = 8272 bytes per allowed skill.
|
|
62
|
-
const allowedBytes = allowlist.length * 8272; // matches the R002 measurement
|
|
63
|
-
const totalBytes = allowedBytes + stubBytes;
|
|
64
|
-
const shadowReductionPct = originalDeniedBytes > 0 ? 1 - stubBytes / originalDeniedBytes : 0;
|
|
65
|
-
// Token estimation: chars / 4 for full skills; bytes * 0.25 for stubs (YAML is denser).
|
|
66
|
-
const allowedTokens = Math.round(allowedBytes / 4);
|
|
67
|
-
const deniedTokens = Math.round(stubBytes * 0.25);
|
|
68
|
-
const totalTokens = allowedTokens + deniedTokens;
|
|
69
|
-
const totals = {
|
|
70
|
-
allowedCount: allowlist.length,
|
|
71
|
-
deniedCount: denied.length,
|
|
72
|
-
allowedBytes,
|
|
73
|
-
stubBytes,
|
|
74
|
-
originalDeniedBytes,
|
|
75
|
-
shadowReductionPct,
|
|
76
|
-
totalBytes,
|
|
77
|
-
};
|
|
78
|
-
const estimatedTokens = {
|
|
79
|
-
allowed: allowedTokens,
|
|
80
|
-
denied: deniedTokens,
|
|
81
|
-
total: totalTokens,
|
|
82
|
-
};
|
|
83
|
-
const data = {
|
|
84
|
-
scope: raw,
|
|
85
|
-
totals,
|
|
86
|
-
estimatedTokens,
|
|
87
|
-
};
|
|
88
|
-
if (!input.json) {
|
|
89
|
-
data.human = [
|
|
90
|
-
`Allowed: ${allowlist.length} skills, ${(allowedBytes / 1024).toFixed(1)} KB / ${(allowedTokens / 1000).toFixed(1)}K tokens.`,
|
|
91
|
-
`Denied: ${denied.length} skills, ${(stubBytes / 1024).toFixed(1)} KB / ${(deniedTokens / 1000).toFixed(1)}K tokens (${(shadowReductionPct * 100).toFixed(1)}% shadow-stub reduction).`,
|
|
92
|
-
`Total: ${(totalBytes / 1024).toFixed(1)} KB / ${(totalTokens / 1000).toFixed(1)}K tokens.`,
|
|
93
|
-
].join('\n');
|
|
94
|
-
}
|
|
95
|
-
return ok('skill.context-stats', data);
|
|
96
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `peaks skill scope` CLI surface (slice 025.1).
|
|
3
|
-
*
|
|
4
|
-
* Four subcommands (mutually exclusive):
|
|
5
|
-
* - `--detect` — dry-run; prints the relevance matrix, never touches files.
|
|
6
|
-
* - `--apply` — writes the source-of-truth + IDE-native config.
|
|
7
|
-
* - `--show` — reads the source-of-truth + native config back.
|
|
8
|
-
* - `--reset` — removes the source-of-truth + IDE-native config.
|
|
9
|
-
*
|
|
10
|
-
* Exit code matrix (tech-doc §6.3):
|
|
11
|
-
* 0 success
|
|
12
|
-
* 1 uncaught error
|
|
13
|
-
* 2 invalid usage (missing/incompatible flags)
|
|
14
|
-
* 3 source-of-truth written but adapter returned NOT_SUPPORTED
|
|
15
|
-
* 4 adapter failure other than NOT_SUPPORTED
|
|
16
|
-
*/
|
|
17
|
-
import { Command } from 'commander';
|
|
18
|
-
import { type ResultEnvelope } from '../../shared/result.js';
|
|
19
|
-
import { type ProgramIO } from '../cli-helpers.js';
|
|
20
|
-
export type SkillScopeAction = 'detect' | 'apply' | 'show' | 'reset';
|
|
21
|
-
export interface RunSkillScopeInput {
|
|
22
|
-
readonly subcommand: SkillScopeAction;
|
|
23
|
-
readonly project: string;
|
|
24
|
-
readonly strict?: boolean;
|
|
25
|
-
readonly loose?: boolean;
|
|
26
|
-
readonly ide?: string;
|
|
27
|
-
readonly shadowFallback?: boolean;
|
|
28
|
-
readonly json?: boolean;
|
|
29
|
-
/** Test seam: override the detected allowlist (CLI re-adds peaks-* per G6). */
|
|
30
|
-
readonly overrideAllowlist?: readonly string[];
|
|
31
|
-
/** Test seam: force the source-of-truth write to fail (simulates atomicity test). */
|
|
32
|
-
readonly simulateSourceOfTruthWriteFailure?: boolean;
|
|
33
|
-
}
|
|
34
|
-
export interface RunSkillScopeResult {
|
|
35
|
-
readonly exitCode: number;
|
|
36
|
-
readonly envelope: ResultEnvelope<unknown> | null;
|
|
37
|
-
readonly stdout: string;
|
|
38
|
-
readonly stderr: string;
|
|
39
|
-
}
|
|
40
|
-
/** Run the --apply subcommand. R003.3: `runApply` is exported for direct testing. */
|
|
41
|
-
export declare function runApply(input: RunSkillScopeInput): Promise<RunSkillScopeResult>;
|
|
42
|
-
/**
|
|
43
|
-
* Programmatic entry point for `peaks skill scope`. Used by the CLI shim
|
|
44
|
-
* AND by the unit tests.
|
|
45
|
-
*/
|
|
46
|
-
export declare function runSkillScopeCommand(input: RunSkillScopeInput): Promise<RunSkillScopeResult>;
|
|
47
|
-
/**
|
|
48
|
-
* Register the `peaks skill scope` subcommand on the `skill` command group.
|
|
49
|
-
* Mutually-exclusive flags: exactly one of --detect / --apply / --show / --reset.
|
|
50
|
-
*/
|
|
51
|
-
export declare function registerSkillScopeCommands(program: Command, io: ProgramIO): void;
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `peaks skill scope` CLI surface (slice 025.1).
|
|
3
|
-
*
|
|
4
|
-
* Four subcommands (mutually exclusive):
|
|
5
|
-
* - `--detect` — dry-run; prints the relevance matrix, never touches files.
|
|
6
|
-
* - `--apply` — writes the source-of-truth + IDE-native config.
|
|
7
|
-
* - `--show` — reads the source-of-truth + native config back.
|
|
8
|
-
* - `--reset` — removes the source-of-truth + IDE-native config.
|
|
9
|
-
*
|
|
10
|
-
* Exit code matrix (tech-doc §6.3):
|
|
11
|
-
* 0 success
|
|
12
|
-
* 1 uncaught error
|
|
13
|
-
* 2 invalid usage (missing/incompatible flags)
|
|
14
|
-
* 3 source-of-truth written but adapter returned NOT_SUPPORTED
|
|
15
|
-
* 4 adapter failure other than NOT_SUPPORTED
|
|
16
|
-
*/
|
|
17
|
-
import { existsSync } from 'node:fs';
|
|
18
|
-
import { join } from 'node:path';
|
|
19
|
-
import { detectSkillScope, } from '../../services/skill-scope/detect.js';
|
|
20
|
-
import { resolveActiveAdapter, getScopeAdapter } from '../../services/skill-scope/registry.js';
|
|
21
|
-
import { ideCompanionFilePath, readIdeCompanion, readSourceOfTruth, removeIfExists, scopeFilePath, writeSourceOfTruth, } from '../../services/skill-scope/source-of-truth.js';
|
|
22
|
-
import { ALWAYS_RELEVANT_SKILLS } from '../../services/skill-scope/types.js';
|
|
23
|
-
import { fail, getErrorMessage, ok } from '../../shared/result.js';
|
|
24
|
-
import { addJsonOption, printResult } from '../cli-helpers.js';
|
|
25
|
-
const VALID_ACTIONS = ['detect', 'apply', 'show', 'reset'];
|
|
26
|
-
const VALID_IDES = ['claude-code', 'trae', 'codex', 'cursor', 'qoder', 'tongyi-lingma'];
|
|
27
|
-
function isValidIde(value) {
|
|
28
|
-
return VALID_IDES.includes(value);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* G6: enforce the peaks-* allowlist. Re-adds any peak-* skill that is
|
|
32
|
-
* missing from the allowlist, and removes any peak-* skill from the
|
|
33
|
-
* denylist. The list is the same one declared in `types.ts`.
|
|
34
|
-
*/
|
|
35
|
-
function enforcePeaksAllowlist(allowlist) {
|
|
36
|
-
const set = new Set(allowlist);
|
|
37
|
-
for (const name of ALWAYS_RELEVANT_SKILLS) {
|
|
38
|
-
if (name.startsWith('peaks-'))
|
|
39
|
-
set.add(name);
|
|
40
|
-
}
|
|
41
|
-
return [...set];
|
|
42
|
-
}
|
|
43
|
-
function stripPeaksFromDenylist(denylist) {
|
|
44
|
-
return denylist.filter((name) => !name.startsWith('peaks-'));
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Determine the IDE. Caller-supplied `--ide` wins; otherwise the registry
|
|
48
|
-
* probes the project root.
|
|
49
|
-
*/
|
|
50
|
-
async function resolveIde(projectRoot, override) {
|
|
51
|
-
if (override !== undefined) {
|
|
52
|
-
if (!isValidIde(override)) {
|
|
53
|
-
throw new Error(`Unknown IDE: ${override}. Valid: ${VALID_IDES.join(', ')}`);
|
|
54
|
-
}
|
|
55
|
-
return { ide: override, isFallback: false };
|
|
56
|
-
}
|
|
57
|
-
const resolved = await resolveActiveAdapter(projectRoot);
|
|
58
|
-
return { ide: resolved.adapter.ide, isFallback: resolved.isFallback };
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Stable timestamp (no millisecond jitter) for the `generatedAt` field.
|
|
62
|
-
* `Date.now()` would still be deterministic per-run; we keep the natural
|
|
63
|
-
* one to ensure `generatedAt` matches what the user sees on disk.
|
|
64
|
-
*/
|
|
65
|
-
function nowIso() {
|
|
66
|
-
return new Date().toISOString();
|
|
67
|
-
}
|
|
68
|
-
/** Run the --detect subcommand. */
|
|
69
|
-
async function runDetect(input) {
|
|
70
|
-
try {
|
|
71
|
-
const result = await detectSkillScope({ projectRoot: input.project });
|
|
72
|
-
const envelope = ok('skill.scope.detect', result);
|
|
73
|
-
const stdout = input.json === true ? JSON.stringify(envelope, null, 2) : JSON.stringify(result, null, 2);
|
|
74
|
-
return { exitCode: 0, envelope, stdout, stderr: '' };
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
const envelope = fail('skill.scope.detect', 'DETECT_FAILED', getErrorMessage(error), null);
|
|
78
|
-
return { exitCode: 1, envelope, stdout: '', stderr: envelope.message ?? 'detect failed' };
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/** Build the final ScopeConfig (applies G6 enforcement + override). */
|
|
82
|
-
function buildScopeConfig(args) {
|
|
83
|
-
const strict = args.strict;
|
|
84
|
-
const detected = args.detected;
|
|
85
|
-
// Build allowlist from detected.relevant + (in loose) borderline.
|
|
86
|
-
const allowFromDetect = detected.skills
|
|
87
|
-
.filter((s) => s.relevance === 'relevant' || (!strict && s.relevance === 'borderline'))
|
|
88
|
-
.map((s) => s.name);
|
|
89
|
-
const merged = args.allowOverride !== undefined ? [...args.allowOverride, ...allowFromDetect] : allowFromDetect;
|
|
90
|
-
const enforced = enforcePeaksAllowlist(merged);
|
|
91
|
-
// Denylist: irrelevant skills (strict + loose both), minus anything in allowlist.
|
|
92
|
-
const denyFromDetect = detected.skills
|
|
93
|
-
.filter((s) => s.relevance === 'irrelevant' && !enforced.includes(s.name))
|
|
94
|
-
.map((s) => s.name);
|
|
95
|
-
const finalDeny = stripPeaksFromDenylist(denyFromDetect);
|
|
96
|
-
return {
|
|
97
|
-
generatedAt: nowIso(),
|
|
98
|
-
ide: args.ide,
|
|
99
|
-
strict,
|
|
100
|
-
allowlist: enforced,
|
|
101
|
-
denylist: finalDeny,
|
|
102
|
-
skills: detected.skills,
|
|
103
|
-
signals: detected.projectSignals,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
/** Run the --apply subcommand. R003.3: `runApply` is exported for direct testing. */
|
|
107
|
-
export async function runApply(input) {
|
|
108
|
-
// 1. Detect the scope.
|
|
109
|
-
const detected = await detectSkillScope({ projectRoot: input.project });
|
|
110
|
-
// --strict wins when both flags are passed. Default is --loose per PRD.
|
|
111
|
-
const isStrict = input.strict === true && input.loose !== true;
|
|
112
|
-
const loose = !isStrict;
|
|
113
|
-
const { ide, isFallback } = await resolveIde(input.project, input.ide);
|
|
114
|
-
const adapter = getScopeAdapter(ide);
|
|
115
|
-
const config = buildScopeConfig({
|
|
116
|
-
ide,
|
|
117
|
-
strict: isStrict,
|
|
118
|
-
detected,
|
|
119
|
-
...(input.overrideAllowlist !== undefined ? { allowOverride: input.overrideAllowlist } : {}),
|
|
120
|
-
});
|
|
121
|
-
// 2. Write the source-of-truth first (atomic). Test seam: simulate failure.
|
|
122
|
-
let writtenFiles = [];
|
|
123
|
-
let sourceWritten = false;
|
|
124
|
-
try {
|
|
125
|
-
if (input.simulateSourceOfTruthWriteFailure) {
|
|
126
|
-
throw new Error('simulated source-of-truth write failure');
|
|
127
|
-
}
|
|
128
|
-
const file = await writeSourceOfTruth(input.project, config);
|
|
129
|
-
writtenFiles.push(file);
|
|
130
|
-
sourceWritten = true;
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
const envelope = fail('skill.scope.apply', 'WRITE_FAILED', getErrorMessage(error), { ide, sourceWritten: false }, ['Fix filesystem permissions on the project root and retry']);
|
|
134
|
-
return { exitCode: 4, envelope, stdout: '', stderr: envelope.message ?? 'write failed' };
|
|
135
|
-
}
|
|
136
|
-
// 3. Call the adapter. Stub adapters return notSupported=true; we surface it.
|
|
137
|
-
const adapterInput = {
|
|
138
|
-
allowlist: config.allowlist,
|
|
139
|
-
denylist: config.denylist,
|
|
140
|
-
strict: config.strict,
|
|
141
|
-
projectRoot: input.project,
|
|
142
|
-
sourceConfig: config,
|
|
143
|
-
// R003.3: default to shadow-fallback=true. Pass `shadowFallback: false` (or the
|
|
144
|
-
// new --no-shadow-fallback CLI flag) to opt out and copy the full SKILL.md.
|
|
145
|
-
shadowFallback: input.shadowFallback !== false,
|
|
146
|
-
};
|
|
147
|
-
let result;
|
|
148
|
-
try {
|
|
149
|
-
result = await adapter.applyScope(adapterInput);
|
|
150
|
-
}
|
|
151
|
-
catch (error) {
|
|
152
|
-
// Roll back the source-of-truth on adapter failure.
|
|
153
|
-
await removeIfExists(scopeFilePath(input.project));
|
|
154
|
-
const envelope = fail('skill.scope.apply', 'ADAPTER_FAILED', getErrorMessage(error), { ide, sourceWritten: false, writtenFiles: [] }, ['Inspect the adapter error and retry']);
|
|
155
|
-
return { exitCode: 4, envelope, stdout: '', stderr: envelope.message ?? 'adapter failed' };
|
|
156
|
-
}
|
|
157
|
-
// The stub adapter also writes the canonical skills.json — that's
|
|
158
|
-
// already on disk from step 2, so its second write is a no-op update.
|
|
159
|
-
const finalWrittenFiles = [...writtenFiles, ...result.writtenFiles];
|
|
160
|
-
const envelope = ok('skill.scope.apply', {
|
|
161
|
-
ide,
|
|
162
|
-
isFallback,
|
|
163
|
-
strict: isStrict,
|
|
164
|
-
loose,
|
|
165
|
-
allowlist: config.allowlist,
|
|
166
|
-
denylist: config.denylist,
|
|
167
|
-
signals: config.signals,
|
|
168
|
-
writtenFiles: finalWrittenFiles,
|
|
169
|
-
usedShadowStub: result.usedShadowStub,
|
|
170
|
-
notSupported: result.notSupported,
|
|
171
|
-
strippedFromDenylist: result.strippedFromDenylist ?? [],
|
|
172
|
-
error: result.error,
|
|
173
|
-
});
|
|
174
|
-
const stdout = input.json === true ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope.data, null, 2);
|
|
175
|
-
if (result.notSupported) {
|
|
176
|
-
// Stub adapter: NOT_SUPPORTED → exit 3, write error to stderr.
|
|
177
|
-
const stderr = `${result.error?.code ?? 'NOT_SUPPORTED'}: ${result.error?.message ?? 'not supported'}`;
|
|
178
|
-
return { exitCode: 3, envelope, stdout, stderr };
|
|
179
|
-
}
|
|
180
|
-
return { exitCode: 0, envelope, stdout, stderr: '' };
|
|
181
|
-
}
|
|
182
|
-
/** Run the --show subcommand. */
|
|
183
|
-
async function runShow(input) {
|
|
184
|
-
const source = await readSourceOfTruth(input.project);
|
|
185
|
-
const { ide } = await resolveIde(input.project, input.ide);
|
|
186
|
-
const companionPath = ideCompanionFilePath(input.project, ide);
|
|
187
|
-
const companion = await readIdeCompanion(input.project, ide);
|
|
188
|
-
// For Claude Code, the native config is `.claude/settings.local.json`.
|
|
189
|
-
const nativeSettingsPath = join(input.project, '.claude', 'settings.local.json');
|
|
190
|
-
const nativeExists = existsSync(nativeSettingsPath);
|
|
191
|
-
let native = companion;
|
|
192
|
-
if (nativeExists) {
|
|
193
|
-
try {
|
|
194
|
-
const { readFile } = await import('node:fs/promises');
|
|
195
|
-
native = JSON.parse(await readFile(nativeSettingsPath, 'utf8'));
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
native = null;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
const data = {
|
|
202
|
-
ide,
|
|
203
|
-
source,
|
|
204
|
-
native,
|
|
205
|
-
nativeSettingsPath: nativeExists ? '.claude/settings.local.json' : null,
|
|
206
|
-
companionPath: existsSync(companionPath) ? companionPath : null,
|
|
207
|
-
};
|
|
208
|
-
const envelope = ok('skill.scope.show', data);
|
|
209
|
-
const stdout = input.json === true ? JSON.stringify(envelope, null, 2) : JSON.stringify(data, null, 2);
|
|
210
|
-
return { exitCode: 0, envelope, stdout, stderr: '' };
|
|
211
|
-
}
|
|
212
|
-
/** Run the --reset subcommand. */
|
|
213
|
-
async function runReset(input) {
|
|
214
|
-
const { ide } = await resolveIde(input.project, input.ide);
|
|
215
|
-
const adapter = getScopeAdapter(ide);
|
|
216
|
-
const resetResult = await adapter.resetScope({ projectRoot: input.project });
|
|
217
|
-
const sourceFile = scopeFilePath(input.project);
|
|
218
|
-
const sourceRemoved = await removeIfExists(sourceFile);
|
|
219
|
-
const allRemoved = [...resetResult.removedFiles, ...(sourceRemoved ? [sourceFile] : [])];
|
|
220
|
-
const envelope = ok('skill.scope.reset', {
|
|
221
|
-
ide,
|
|
222
|
-
removedFiles: allRemoved,
|
|
223
|
-
});
|
|
224
|
-
// Always include the canonical source-of-truth path in the human-readable
|
|
225
|
-
// summary, even if it didn't exist (so the user knows what was targeted).
|
|
226
|
-
const displayFiles = allRemoved.length > 0 ? allRemoved : [sourceFile, join(input.project, '.claude', 'settings.local.json')];
|
|
227
|
-
const summary = `removed: ${displayFiles.join(', ')}`;
|
|
228
|
-
const stdout = input.json === true ? JSON.stringify(envelope, null, 2) : summary;
|
|
229
|
-
return { exitCode: 0, envelope, stdout, stderr: '' };
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Programmatic entry point for `peaks skill scope`. Used by the CLI shim
|
|
233
|
-
* AND by the unit tests.
|
|
234
|
-
*/
|
|
235
|
-
export async function runSkillScopeCommand(input) {
|
|
236
|
-
if (!VALID_ACTIONS.includes(input.subcommand)) {
|
|
237
|
-
const envelope = fail('skill.scope', 'INVALID_USAGE', `Unknown action: ${input.subcommand}`, null);
|
|
238
|
-
return { exitCode: 2, envelope, stdout: '', stderr: envelope.message ?? 'invalid usage' };
|
|
239
|
-
}
|
|
240
|
-
switch (input.subcommand) {
|
|
241
|
-
case 'detect': return runDetect(input);
|
|
242
|
-
case 'apply': return runApply(input);
|
|
243
|
-
case 'show': return runShow(input);
|
|
244
|
-
case 'reset': return runReset(input);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Register the `peaks skill scope` subcommand on the `skill` command group.
|
|
249
|
-
* Mutually-exclusive flags: exactly one of --detect / --apply / --show / --reset.
|
|
250
|
-
*/
|
|
251
|
-
export function registerSkillScopeCommands(program, io) {
|
|
252
|
-
// Find the existing 'skill' subcommand if any.
|
|
253
|
-
let skillCmd = program.commands.find((c) => c.name() === 'skill');
|
|
254
|
-
if (skillCmd === undefined) {
|
|
255
|
-
skillCmd = program.command('skill').description('Manage Peaks skills');
|
|
256
|
-
}
|
|
257
|
-
const scope = skillCmd
|
|
258
|
-
.command('scope')
|
|
259
|
-
.description('Per-project skill scoping: detect, apply, show, reset');
|
|
260
|
-
addJsonOption(scope
|
|
261
|
-
.option('--detect', 'dry-run: print the relevance matrix')
|
|
262
|
-
.option('--apply', 'apply the scope (writes source-of-truth + IDE config)')
|
|
263
|
-
.option('--show', 'show the currently applied scope')
|
|
264
|
-
.option('--reset', 'remove the scope config')
|
|
265
|
-
.option('--project <path>', 'target project root (defaults to cwd)', process.cwd())
|
|
266
|
-
.option('--strict', '--apply: only `relevant` skills in the allowlist')
|
|
267
|
-
.option('--loose', '--apply: `relevant` + `borderline` in the allowlist (default)')
|
|
268
|
-
.option('--ide <name>', 'force a specific IDE adapter (overrides auto-detect)')
|
|
269
|
-
// R003.3: --shadow-fallback is the new default. Pass --no-shadow-fallback
|
|
270
|
-
// to opt out (copy the full SKILL.md to .claude/skills/ for IDE-side inspection).
|
|
271
|
-
.option('--shadow-fallback', '--apply: Claude Code uses shadow stubs for the denylist (default)')
|
|
272
|
-
.option('--no-shadow-fallback', '--apply: copy the full SKILL.md for the denylist (opt out of shadowing)')).action(async (options) => {
|
|
273
|
-
const flags = [options.detect, options.apply, options.show, options.reset].filter(Boolean).length;
|
|
274
|
-
if (flags !== 1) {
|
|
275
|
-
const envelope = fail('skill.scope', 'INVALID_USAGE', 'Exactly one of --detect / --apply / --show / --reset is required', null, ['Pass exactly one action flag']);
|
|
276
|
-
printResult(io, envelope, options.json === true);
|
|
277
|
-
process.exitCode = 2;
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
const subcommand = options.detect
|
|
281
|
-
? 'detect'
|
|
282
|
-
: options.apply
|
|
283
|
-
? 'apply'
|
|
284
|
-
: options.show
|
|
285
|
-
? 'show'
|
|
286
|
-
: 'reset';
|
|
287
|
-
const result = await runSkillScopeCommand({
|
|
288
|
-
subcommand,
|
|
289
|
-
project: options.project ?? process.cwd(),
|
|
290
|
-
...(options.strict !== undefined ? { strict: options.strict } : {}),
|
|
291
|
-
...(options.loose !== undefined ? { loose: options.loose } : {}),
|
|
292
|
-
...(options.ide !== undefined ? { ide: options.ide } : {}),
|
|
293
|
-
...(options.shadowFallback !== undefined ? { shadowFallback: options.shadowFallback } : {}),
|
|
294
|
-
...(options.json !== undefined ? { json: options.json } : {}),
|
|
295
|
-
});
|
|
296
|
-
if (options.json === true) {
|
|
297
|
-
if (result.envelope !== null)
|
|
298
|
-
printResult(io, result.envelope, true);
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
if (result.stdout.length > 0)
|
|
302
|
-
io.stdout(result.stdout);
|
|
303
|
-
if (result.stderr.length > 0)
|
|
304
|
-
io.stderr(result.stderr);
|
|
305
|
-
}
|
|
306
|
-
if (result.exitCode !== 0) {
|
|
307
|
-
process.exitCode = result.exitCode;
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
declare const SHADCN_PACKAGE_NAME = "shadcn";
|
|
2
|
-
declare const SHADCN_PACKAGE_VERSION = "4.7.0";
|
|
3
|
-
declare const SHADCN_EXECUTABLE: string;
|
|
4
|
-
export type ShadcnInvocationOptions = {
|
|
5
|
-
args: string[];
|
|
6
|
-
cwd?: string;
|
|
7
|
-
};
|
|
8
|
-
export type ShadcnInvocation = {
|
|
9
|
-
executable: typeof SHADCN_EXECUTABLE;
|
|
10
|
-
args: string[];
|
|
11
|
-
cwd: string;
|
|
12
|
-
packageName: typeof SHADCN_PACKAGE_NAME;
|
|
13
|
-
packageVersion: typeof SHADCN_PACKAGE_VERSION;
|
|
14
|
-
};
|
|
15
|
-
export type ShadcnExecutionResult = {
|
|
16
|
-
exitCode: number | null;
|
|
17
|
-
stdout: string;
|
|
18
|
-
stderr: string;
|
|
19
|
-
};
|
|
20
|
-
export type ShadcnProcessRunner = (invocation: ShadcnInvocation) => Promise<ShadcnExecutionResult>;
|
|
21
|
-
declare function createShadcnEnvironment(sourceEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
22
|
-
export declare function createShadcnInvocation(options: ShadcnInvocationOptions): ShadcnInvocation;
|
|
23
|
-
export declare function executeShadcnInvocation(invocation: ShadcnInvocation, runner?: ShadcnProcessRunner): Promise<ShadcnExecutionResult>;
|
|
24
|
-
export declare const testInternals: {
|
|
25
|
-
createShadcnEnvironment: typeof createShadcnEnvironment;
|
|
26
|
-
};
|
|
27
|
-
export {};
|