peaks-cli 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/src/cli/commands/core-artifact-commands.js +21 -0
  2. package/dist/src/cli/commands/memory-commands.d.ts +13 -0
  3. package/dist/src/cli/commands/memory-commands.js +60 -0
  4. package/dist/src/cli/commands/migrate-1-4-1-command.d.ts +11 -0
  5. package/dist/src/cli/commands/migrate-1-4-1-command.js +34 -0
  6. package/dist/src/cli/commands/retrospective-commands.d.ts +9 -0
  7. package/dist/src/cli/commands/retrospective-commands.js +58 -0
  8. package/dist/src/cli/commands/workspace-commands.js +8 -0
  9. package/dist/src/cli/program.js +16 -22
  10. package/dist/src/services/fuzzy-matching/fuzzy-match-service.d.ts +15 -0
  11. package/dist/src/services/fuzzy-matching/fuzzy-match-service.js +56 -0
  12. package/dist/src/services/fuzzy-matching/types.d.ts +20 -0
  13. package/dist/src/services/fuzzy-matching/types.js +1 -0
  14. package/dist/src/services/memory/memory-search-service.d.ts +61 -0
  15. package/dist/src/services/memory/memory-search-service.js +80 -0
  16. package/dist/src/services/recommendations/capability-seed-items.js +0 -1
  17. package/dist/src/services/recommendations/capability-seed-mappings.js +0 -1
  18. package/dist/src/services/recommendations/capability-seed-sources.js +0 -1
  19. package/dist/src/services/retrospective/retrospective-search-service.d.ts +37 -0
  20. package/dist/src/services/retrospective/retrospective-search-service.js +75 -0
  21. package/dist/src/services/standards/project-context.d.ts +1 -1
  22. package/dist/src/services/standards/project-context.js +0 -4
  23. package/dist/src/services/standards/project-standards-service.js +1 -3
  24. package/dist/src/services/workspace/migrate-1-4-1-service.d.ts +44 -0
  25. package/dist/src/services/workspace/migrate-1-4-1-service.js +195 -0
  26. package/dist/src/shared/version.d.ts +1 -1
  27. package/dist/src/shared/version.js +1 -1
  28. package/package.json +3 -7
  29. package/skills/peaks-solo/SKILL.md +1 -1
  30. package/skills/peaks-solo/references/completion-handoff.md +3 -1
  31. package/dist/src/cli/commands/shadcn-commands.d.ts +0 -3
  32. package/dist/src/cli/commands/shadcn-commands.js +0 -35
  33. package/dist/src/cli/commands/skill-scope-commands.d.ts +0 -49
  34. package/dist/src/cli/commands/skill-scope-commands.js +0 -305
  35. package/dist/src/services/shadcn/shadcn-service.d.ts +0 -27
  36. package/dist/src/services/shadcn/shadcn-service.js +0 -128
  37. package/dist/src/services/skill-scope/adapters/_stub-helper.d.ts +0 -39
  38. package/dist/src/services/skill-scope/adapters/_stub-helper.js +0 -98
  39. package/dist/src/services/skill-scope/adapters/claude-code.d.ts +0 -59
  40. package/dist/src/services/skill-scope/adapters/claude-code.js +0 -304
  41. package/dist/src/services/skill-scope/adapters/codex.d.ts +0 -2
  42. package/dist/src/services/skill-scope/adapters/codex.js +0 -12
  43. package/dist/src/services/skill-scope/adapters/cursor.d.ts +0 -2
  44. package/dist/src/services/skill-scope/adapters/cursor.js +0 -13
  45. package/dist/src/services/skill-scope/adapters/qoder.d.ts +0 -2
  46. package/dist/src/services/skill-scope/adapters/qoder.js +0 -13
  47. package/dist/src/services/skill-scope/adapters/tongyi.d.ts +0 -2
  48. package/dist/src/services/skill-scope/adapters/tongyi.js +0 -13
  49. package/dist/src/services/skill-scope/adapters/trae.d.ts +0 -2
  50. package/dist/src/services/skill-scope/adapters/trae.js +0 -12
  51. package/dist/src/services/skill-scope/detect.d.ts +0 -75
  52. package/dist/src/services/skill-scope/detect.js +0 -480
  53. package/dist/src/services/skill-scope/registry.d.ts +0 -41
  54. package/dist/src/services/skill-scope/registry.js +0 -83
  55. package/dist/src/services/skill-scope/source-of-truth.d.ts +0 -44
  56. package/dist/src/services/skill-scope/source-of-truth.js +0 -118
  57. package/dist/src/services/skill-scope/types.d.ts +0 -176
  58. package/dist/src/services/skill-scope/types.js +0 -74
@@ -0,0 +1,37 @@
1
+ export type { RetrospectiveType, RetrospectiveOutcome, RetrospectiveEntry } from './retrospective-index.js';
2
+ import type { RetrospectiveType, RetrospectiveOutcome } from './retrospective-index.js';
3
+ /**
4
+ * Input to `searchRetrospective`. `projectRoot` defaults to `process.cwd()`.
5
+ * `query` is required and non-empty. `type` and `outcome` are optional
6
+ * structured filters that compose with AND.
7
+ */
8
+ export interface RetrospectiveSearchInput {
9
+ query: string;
10
+ projectRoot?: string;
11
+ limit?: number;
12
+ type?: RetrospectiveType;
13
+ outcome?: RetrospectiveOutcome;
14
+ }
15
+ /**
16
+ * One hit returned by `searchRetrospective`. The `artifactPaths` are
17
+ * preserved so the LLM can follow up with `peaks retrospective show
18
+ * <id>` (or read the artifact directly).
19
+ */
20
+ export interface RetrospectiveSearchResult {
21
+ id: string;
22
+ sessionId: string;
23
+ type: RetrospectiveType;
24
+ title: string;
25
+ summary: string;
26
+ outcome: RetrospectiveOutcome;
27
+ artifactPaths: string[];
28
+ score: number;
29
+ positions: number[];
30
+ }
31
+ /**
32
+ * Run the generic fuzzy kernel against the on-disk retrospective index.
33
+ * Searchable text is `title + " " + summary` per spec §Component Details.
34
+ * `--type` and `--outcome` filters compose with AND before the kernel
35
+ * runs (cheaper to filter, then fuzzy on a smaller set).
36
+ */
37
+ export declare function searchRetrospective(input: RetrospectiveSearchInput): RetrospectiveSearchResult[];
@@ -0,0 +1,75 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { fuzzyMatchWithKey } from '../fuzzy-matching/fuzzy-match-service.js';
4
+ const DEFAULT_LIMIT = 6;
5
+ /**
6
+ * Read `.peaks/retrospective/index.json` and return the entries.
7
+ * Throws structured errors with stable codes that the CLI converts
8
+ * to the peaks envelope.
9
+ */
10
+ function readIndex(projectRoot) {
11
+ const resolvedRoot = resolve(projectRoot);
12
+ const indexPath = join(resolvedRoot, '.peaks', 'retrospective', 'index.json');
13
+ if (!existsSync(indexPath)) {
14
+ const err = new Error(`INDEX_MISSING: retrospective index not found at ${indexPath}`);
15
+ err.code = 'INDEX_MISSING';
16
+ throw err;
17
+ }
18
+ let raw;
19
+ try {
20
+ raw = readFileSync(indexPath, 'utf8');
21
+ }
22
+ catch (cause) {
23
+ const err = new Error(`INDEX_INVALID: failed to read retrospective index at ${indexPath}: ${cause.message}`);
24
+ err.code = 'INDEX_INVALID';
25
+ throw err;
26
+ }
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(raw);
30
+ }
31
+ catch (cause) {
32
+ const err = new Error(`INDEX_INVALID: malformed retrospective index at ${indexPath}: ${cause.message}`);
33
+ err.code = 'INDEX_INVALID';
34
+ throw err;
35
+ }
36
+ const index = parsed;
37
+ return Array.isArray(index.entries) ? index.entries : [];
38
+ }
39
+ /**
40
+ * Run the generic fuzzy kernel against the on-disk retrospective index.
41
+ * Searchable text is `title + " " + summary` per spec §Component Details.
42
+ * `--type` and `--outcome` filters compose with AND before the kernel
43
+ * runs (cheaper to filter, then fuzzy on a smaller set).
44
+ */
45
+ export function searchRetrospective(input) {
46
+ if (input.query === '') {
47
+ const err = new Error('EMPTY_QUERY: searchRetrospective requires a non-empty query (use `peaks retrospective index` to list all)');
48
+ err.code = 'EMPTY_QUERY';
49
+ throw err;
50
+ }
51
+ const projectRoot = input.projectRoot ?? process.cwd();
52
+ const limit = input.limit ?? DEFAULT_LIMIT;
53
+ let candidates = readIndex(projectRoot);
54
+ if (input.type !== undefined) {
55
+ candidates = candidates.filter((e) => e.type === input.type);
56
+ }
57
+ if (input.outcome !== undefined) {
58
+ candidates = candidates.filter((e) => e.outcome === input.outcome);
59
+ }
60
+ const matches = fuzzyMatchWithKey(input.query, candidates, { keyFn: (e) => `${e.title} ${e.summary}`, limit, caseSensitive: false });
61
+ return matches.map((m) => {
62
+ const entry = m.item;
63
+ return {
64
+ id: entry.id,
65
+ sessionId: entry.sessionId,
66
+ type: entry.type,
67
+ title: entry.title,
68
+ summary: entry.summary,
69
+ outcome: entry.outcome,
70
+ artifactPaths: entry.artifactPaths,
71
+ score: m.score,
72
+ positions: m.positions,
73
+ };
74
+ });
75
+ }
@@ -1,6 +1,6 @@
1
1
  export type BuildTool = 'umi' | 'next' | 'vite' | 'rsbuild' | 'rspack' | 'farm' | 'craco' | 'webpack' | 'gulp' | 'angular' | 'custom' | 'unknown';
2
2
  export type ComponentLibrary = {
3
- readonly name: 'antd' | 'antd-pro' | 'mui' | 'shadcn' | 'element-plus' | 'element-ui' | 'arco' | 'tdesign' | 'semi' | 'nextui' | 'chakra' | 'vant' | 'none';
3
+ readonly name: 'antd' | 'antd-pro' | 'mui' | 'element-plus' | 'element-ui' | 'arco' | 'tdesign' | 'semi' | 'nextui' | 'chakra' | 'vant' | 'none';
4
4
  readonly majorVersion?: string;
5
5
  readonly hasProSuite?: boolean;
6
6
  };
@@ -64,8 +64,6 @@ function detectComponentLibrary(deps) {
64
64
  }
65
65
  if ('@mui/material' in deps)
66
66
  return { name: 'mui', ...(majorOf(deps['@mui/material']) !== undefined ? { majorVersion: majorOf(deps['@mui/material']) } : {}) };
67
- if ('tailwindcss' in deps && Object.keys(deps).some((d) => d.startsWith('@radix-ui/')))
68
- return { name: 'shadcn' };
69
67
  if ('element-plus' in deps)
70
68
  return { name: 'element-plus' };
71
69
  if ('element-ui' in deps)
@@ -279,8 +277,6 @@ export function componentLibraryLabel(lib) {
279
277
  return 'Ant Design + Ant Design Pro';
280
278
  case 'mui':
281
279
  return 'Material UI';
282
- case 'shadcn':
283
- return 'shadcn/ui (Tailwind + Radix)';
284
280
  case 'element-plus':
285
281
  return 'Element Plus';
286
282
  case 'element-ui':
@@ -159,8 +159,6 @@ function renderCommonCodingStyle(ctx) {
159
159
  }
160
160
  if (lib === 'mui')
161
161
  stackRules.push('- Style MUI via `sx`, `styled()`, and `theme`. Do NOT apply TailwindCSS utility classes directly to MUI components.');
162
- if (lib === 'shadcn')
163
- stackRules.push('- Use existing shadcn component variants and Tailwind utility classes. Do not introduce a competing component library.');
164
162
  if (ctx.cssFrameworks.includes('tailwind') && (lib === 'antd' || lib === 'antd-pro' || lib === 'mui')) {
165
163
  stackRules.push('- TailwindCSS is for layout/utility only; component-library tokens own component styling.');
166
164
  }
@@ -185,7 +183,7 @@ function renderCodeReview(ctx) {
185
183
  const extra = [];
186
184
  const lib = ctx.componentLibrary.name;
187
185
  if (lib === 'antd' || lib === 'antd-pro') {
188
- extra.push('- Block PRs that introduce a second component library (MUI/shadcn/Chakra) alongside antd.');
186
+ extra.push('- Block PRs that introduce a second component library (MUI/Chakra) alongside antd.');
189
187
  extra.push('- Block PRs that import antd v3/v4 APIs in this v5 project, or vice versa.');
190
188
  }
191
189
  if (ctx.cssFrameworks.includes('tailwind') && (lib === 'antd' || lib === 'antd-pro' || lib === 'mui')) {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `peaks workspace migrate-1-4-1` — R004.
3
+ *
4
+ * Cleanup command for projects upgraded from peaks-cli 1.4.1 → 1.4.2.
5
+ *
6
+ * Slice 006 (1.4.0) moved the canonical per-session root to
7
+ * `.peaks/_runtime/<sid>/`. Per-request artifacts (PRD, RD, QA, SC requests)
8
+ * were written to the new root, but per-session artifacts (tech-doc.md,
9
+ * code-review.md, test-cases/<rid>.md, etc.) were kept at the legacy
10
+ * `.peaks/<sid>/<role>/<file>.md` path. The 2-tier fallback in
11
+ * `resolvePrerequisiteAbsolutePathWithFallback` accepts either location, so
12
+ * the functional behavior is correct, but the user's filesystem has visible
13
+ * dual-path duplication ("飘逸" — the user's term for the UX).
14
+ *
15
+ * R004 ships this command to physically move the legacy per-session files
16
+ * into the canonical `_runtime/<sid>/<role>/` location. After this runs,
17
+ * the project has a single canonical tree; the legacy `<sid>/<role>/`
18
+ * directories are removed (only if empty after the move).
19
+ *
20
+ * Default: dry-run. Pass `--apply` to actually move.
21
+ */
22
+ export declare const PER_SESSION_ARTIFACT_TYPES: readonly ["rd/tech-doc.md", "rd/code-review.md", "rd/security-review.md", "rd/perf-baseline.md", "rd/bug-analysis.md", "qa/security-findings.md", "qa/performance-findings.md"];
23
+ export declare const PER_REQUEST_ARTIFACT_TYPES: readonly ["qa/test-cases/<rid>.md", "qa/test-reports/<rid>.md"];
24
+ export type MigrationPlanEntry = {
25
+ readonly sessionId: string;
26
+ readonly relativePath: string;
27
+ readonly from: string;
28
+ readonly to: string;
29
+ readonly sha256: string;
30
+ readonly reason: 'legacy-only' | 'identical-content-already-canonical' | 'content-mismatch' | 'no-legacy-file';
31
+ };
32
+ export type MigrationResult = {
33
+ readonly plan: ReadonlyArray<MigrationPlanEntry>;
34
+ readonly applied: boolean;
35
+ readonly movedCount: number;
36
+ readonly conflictCount: number;
37
+ readonly deletedEmptyDirs: ReadonlyArray<string>;
38
+ readonly errors: ReadonlyArray<{
39
+ path: string;
40
+ message: string;
41
+ }>;
42
+ };
43
+ export declare function planMigrate1_4_1(projectRoot: string): MigrationResult;
44
+ export declare function applyMigrate1_4_1(projectRoot: string): MigrationResult;
@@ -0,0 +1,195 @@
1
+ /**
2
+ * `peaks workspace migrate-1-4-1` — R004.
3
+ *
4
+ * Cleanup command for projects upgraded from peaks-cli 1.4.1 → 1.4.2.
5
+ *
6
+ * Slice 006 (1.4.0) moved the canonical per-session root to
7
+ * `.peaks/_runtime/<sid>/`. Per-request artifacts (PRD, RD, QA, SC requests)
8
+ * were written to the new root, but per-session artifacts (tech-doc.md,
9
+ * code-review.md, test-cases/<rid>.md, etc.) were kept at the legacy
10
+ * `.peaks/<sid>/<role>/<file>.md` path. The 2-tier fallback in
11
+ * `resolvePrerequisiteAbsolutePathWithFallback` accepts either location, so
12
+ * the functional behavior is correct, but the user's filesystem has visible
13
+ * dual-path duplication ("飘逸" — the user's term for the UX).
14
+ *
15
+ * R004 ships this command to physically move the legacy per-session files
16
+ * into the canonical `_runtime/<sid>/<role>/` location. After this runs,
17
+ * the project has a single canonical tree; the legacy `<sid>/<role>/`
18
+ * directories are removed (only if empty after the move).
19
+ *
20
+ * Default: dry-run. Pass `--apply` to actually move.
21
+ */
22
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync } from 'node:fs';
23
+ import { join } from 'node:path';
24
+ export const PER_SESSION_ARTIFACT_TYPES = [
25
+ 'rd/tech-doc.md',
26
+ 'rd/code-review.md',
27
+ 'rd/security-review.md',
28
+ 'rd/perf-baseline.md',
29
+ 'rd/bug-analysis.md',
30
+ 'qa/security-findings.md',
31
+ 'qa/performance-findings.md',
32
+ ];
33
+ export const PER_REQUEST_ARTIFACT_TYPES = [
34
+ 'qa/test-cases/<rid>.md',
35
+ 'qa/test-reports/<rid>.md',
36
+ ];
37
+ import { createHash } from 'node:crypto';
38
+ function sha256(content) {
39
+ return createHash('sha256').update(content).digest('hex');
40
+ }
41
+ function enumerateLegacySessions(projectRoot) {
42
+ // Legacy per-session dirs: `.peaks/<sid>/` (NOT `.peaks/_runtime/<sid>/`).
43
+ // We skip well-known non-session entries.
44
+ const SKIP = new Set(['memory', 'PROJECT.md', 'retrospective', 'scope', '.peaks-init-hooks-decision.json', 'session.json', '.session.json', '_runtime', '_sub_agents', 'change', 'caller', 'callers', 'sop-state', 'system', 'active-skill.json', '.active-skill.json']);
45
+ const peaksRoot = join(projectRoot, '.peaks');
46
+ if (!existsSync(peaksRoot))
47
+ return [];
48
+ const out = [];
49
+ for (const entry of readdirSync(peaksRoot)) {
50
+ if (SKIP.has(entry))
51
+ continue;
52
+ if (entry.startsWith('.'))
53
+ continue;
54
+ // Treat any directory under .peaks/ that doesn't match a known non-session
55
+ // directory as a candidate session id. The session id is a dated prefix.
56
+ if (entry.match(/^\d{4}-\d{2}-\d{2}-session-/))
57
+ out.push(entry);
58
+ }
59
+ return out;
60
+ }
61
+ function listRequestIdsForSession(legacySessionRoot) {
62
+ // Per-request artifacts use file id like `001-r001.md`. We scan the
63
+ // requests dir to find existing rids.
64
+ const ids = new Set();
65
+ for (const role of ['prd', 'rd', 'qa', 'sc']) {
66
+ const dir = join(legacySessionRoot, role, 'requests');
67
+ if (!existsSync(dir))
68
+ continue;
69
+ for (const f of readdirSync(dir)) {
70
+ const m = f.match(/^\d+-(r\d+)\.md$/);
71
+ if (m && m[1])
72
+ ids.add(m[1]);
73
+ }
74
+ }
75
+ return [...ids];
76
+ }
77
+ function buildPlan(projectRoot) {
78
+ const plan = [];
79
+ for (const sid of enumerateLegacySessions(projectRoot)) {
80
+ const legacyRoot = join(projectRoot, '.peaks', sid);
81
+ const canonicalRoot = join(projectRoot, '.peaks', '_runtime', sid);
82
+ // Per-session files (no <rid> template).
83
+ for (const rel of PER_SESSION_ARTIFACT_TYPES) {
84
+ const from = join(legacyRoot, rel);
85
+ if (!existsSync(from))
86
+ continue;
87
+ const content = readFileSync(from, 'utf8');
88
+ const hash = sha256(content);
89
+ const to = join(canonicalRoot, rel);
90
+ if (existsSync(to)) {
91
+ const existing = readFileSync(to, 'utf8');
92
+ if (sha256(existing) === hash) {
93
+ plan.push({ sessionId: sid, relativePath: rel, from, to, sha256: hash, reason: 'identical-content-already-canonical' });
94
+ }
95
+ else {
96
+ plan.push({ sessionId: sid, relativePath: rel, from, to, sha256: hash, reason: 'content-mismatch' });
97
+ }
98
+ }
99
+ else {
100
+ plan.push({ sessionId: sid, relativePath: rel, from, to, sha256: hash, reason: 'legacy-only' });
101
+ }
102
+ }
103
+ // Per-request files (with <rid> template).
104
+ const rids = listRequestIdsForSession(legacyRoot);
105
+ for (const rel of PER_REQUEST_ARTIFACT_TYPES) {
106
+ for (const rid of rids) {
107
+ const expanded = rel.replace('<rid>', rid);
108
+ const from = join(legacyRoot, expanded);
109
+ if (!existsSync(from))
110
+ continue;
111
+ const content = readFileSync(from, 'utf8');
112
+ const hash = sha256(content);
113
+ const to = join(canonicalRoot, expanded);
114
+ if (existsSync(to)) {
115
+ const existing = readFileSync(to, 'utf8');
116
+ if (sha256(existing) === hash) {
117
+ plan.push({ sessionId: sid, relativePath: expanded, from, to, sha256: hash, reason: 'identical-content-already-canonical' });
118
+ }
119
+ else {
120
+ plan.push({ sessionId: sid, relativePath: expanded, from, to, sha256: hash, reason: 'content-mismatch' });
121
+ }
122
+ }
123
+ else {
124
+ plan.push({ sessionId: sid, relativePath: expanded, from, to, sha256: hash, reason: 'legacy-only' });
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return plan;
130
+ }
131
+ export function planMigrate1_4_1(projectRoot) {
132
+ const plan = buildPlan(projectRoot);
133
+ return {
134
+ plan,
135
+ applied: false,
136
+ movedCount: 0,
137
+ conflictCount: plan.filter((p) => p.reason === 'content-mismatch').length,
138
+ deletedEmptyDirs: [],
139
+ errors: [],
140
+ };
141
+ }
142
+ export function applyMigrate1_4_1(projectRoot) {
143
+ const plan = buildPlan(projectRoot);
144
+ const errors = [];
145
+ let movedCount = 0;
146
+ const deletedEmptyDirs = [];
147
+ const movedFiles = [];
148
+ for (const entry of plan) {
149
+ try {
150
+ if (entry.reason === 'legacy-only') {
151
+ // Move: ensure target dir exists, then rename.
152
+ mkdirSync(join(entry.to, '..'), { recursive: true });
153
+ renameSync(entry.from, entry.to);
154
+ movedFiles.push(entry.from);
155
+ movedCount++;
156
+ }
157
+ else if (entry.reason === 'identical-content-already-canonical') {
158
+ // Skip the move but delete the duplicate source.
159
+ try {
160
+ rmSync(entry.from);
161
+ }
162
+ catch { /* best-effort */ }
163
+ }
164
+ else if (entry.reason === 'content-mismatch') {
165
+ // Conflict: do NOT delete the source. Mark for manual review.
166
+ errors.push({ path: entry.from, message: `content mismatch; review manually (target ${entry.to})` });
167
+ }
168
+ }
169
+ catch (err) {
170
+ errors.push({ path: entry.from, message: err.message });
171
+ }
172
+ }
173
+ // After all moves, clean up legacy session dirs. They're now empty
174
+ // (all files moved; per-role subdirs are also empty). Force-remove the
175
+ // entire tree — if any non-empty dir remains it's a pre-existing file
176
+ // that wasn't part of the migrate plan, which is fine to keep.
177
+ for (const sid of new Set(plan.map((p) => p.sessionId))) {
178
+ const legacyRoot = join(projectRoot, '.peaks', sid);
179
+ try {
180
+ if (existsSync(legacyRoot)) {
181
+ rmSync(legacyRoot, { recursive: true, force: true });
182
+ deletedEmptyDirs.push(legacyRoot);
183
+ }
184
+ }
185
+ catch { /* best-effort */ }
186
+ }
187
+ return {
188
+ plan,
189
+ applied: true,
190
+ movedCount,
191
+ conflictCount: plan.filter((p) => p.reason === 'content-mismatch').length,
192
+ deletedEmptyDirs,
193
+ errors,
194
+ };
195
+ }
@@ -1 +1 @@
1
- export declare const CLI_VERSION = "1.4.0";
1
+ export declare const CLI_VERSION = "1.4.2";
@@ -1 +1 @@
1
- export const CLI_VERSION = "1.4.0";
1
+ export const CLI_VERSION = "1.4.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-cli",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "Cross-AI-IDE workflow-gating CLI + skill family (Claude Code shipped, Trae in progress; Codex / Cursor / Qoder / Tongyi Lingma on the roadmap).",
5
5
  "author": "SquabbyZ",
6
6
  "license": "MIT",
@@ -55,15 +55,11 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@colbymchenry/codegraph": "0.7.10",
58
- "chalk": "^5.6.2",
59
58
  "commander": "^12.1.0",
60
- "headroom-ai": "0.22.4",
61
- "ora": "^8.2.0",
62
- "shadcn": "4.7.0",
63
- "terminal-kit": "^3.1.2"
59
+ "fzf": "^0.5.2",
60
+ "headroom-ai": "0.22.4"
64
61
  },
65
62
  "devDependencies": {
66
- "@types/chalk": "^2.2.4",
67
63
  "@types/node": "^22.10.2",
68
64
  "@vitest/coverage-v8": "^2.1.8",
69
65
  "tsx": "^4.19.2",
@@ -185,7 +185,7 @@ Five CLI commands harden the workflow against silent skips: `peaks request lint`
185
185
 
186
186
  ## Peaks-Cli Completion handoff
187
187
 
188
- After final validation, refresh project-local standards via `peaks standards init/update` (never hand-write). Use Peaks-Cli TXT for the compact handoff capsule: mode, validated decisions, artifact paths, standards deltas, open questions, next action. Do NOT call `peaks skill presence:clear --project <repo>` at workflow end (presence remains active for follow-ups).
188
+ After final validation, refresh project-local standards via `peaks standards init/update` (never hand-write). Use Peaks-Cli TXT for the compact handoff capsule: mode, validated decisions, artifact paths, standards deltas, open questions, next action. **Presence management is delegated to the last downstream skill in the workflow** — peaks-solo does not call `peaks skill presence:clear` itself, and does not enforce a "no clear" rule. The downstream skills (peaks-rd, peaks-qa, peaks-txt) each manage their own presence per their respective SKILL.md.
189
189
 
190
190
  → see `references/completion-handoff.md` for the full handoff + "no auto-exit" rule.
191
191
 
@@ -8,7 +8,9 @@ Use Peaks-Cli TXT for the compact handoff capsule: mode, validated decisions, ar
8
8
 
9
9
  ## Workflow completion (no auto-exit)
10
10
 
11
- Do NOT call `peaks skill presence:clear --project <repo>` at workflow end. The presence file and header remain active so the user stays inside the workflow context. The user can continue with follow-up requirements naturally no need to re-invoke `/peaks-solo`. The header continues to display the active skill and current gate.
11
+ peaks-solo does NOT itself call `peaks skill presence:clear --project <repo>` at workflow end. Presence management is delegated to the last downstream skill in the workflow (peaks-rd, peaks-qa, peaks-txt); each of those skills owns its own presence:clear step per its SKILL.md. peaks-solo only sets presence: it does not unset it.
12
+
13
+ The user can continue with follow-up requirements naturally — no need to re-invoke `/peaks-solo` to do so. The header continues to display whatever skill is active; the user can `/peaks-solo` again to re-anchor.
12
14
 
13
15
  Before ending, extract durable memories from this session:
14
16
  ```bash
@@ -1,3 +0,0 @@
1
- import { Command } from 'commander';
2
- import { type ProgramIO } from '../cli-helpers.js';
3
- export declare function registerShadcnCommands(program: Command, io: ProgramIO): void;
@@ -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,49 +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
- /**
41
- * Programmatic entry point for `peaks skill scope`. Used by the CLI shim
42
- * AND by the unit tests.
43
- */
44
- export declare function runSkillScopeCommand(input: RunSkillScopeInput): Promise<RunSkillScopeResult>;
45
- /**
46
- * Register the `peaks skill scope` subcommand on the `skill` command group.
47
- * Mutually-exclusive flags: exactly one of --detect / --apply / --show / --reset.
48
- */
49
- export declare function registerSkillScopeCommands(program: Command, io: ProgramIO): void;