peaks-loop 3.0.1 → 3.0.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.
package/CHANGELOG.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased] — 2026-06-29 — change-id shim (cleanup post v2.17.0 hard-kill)
3
+ ## [3.0.2] — 2026-07-02 — change-id shim retirement (v2.19.0) + Understand Anything hybrid context (3.0.2)
4
4
 
5
- **MINOR bump target: v2.19.0**. Full removal of the peaks-loop change-id axis (filesystem axis, code shim, envelope JSON slug, CLI flags). OpenSpec's independent `openspec/changes/<change-id>/` vocabulary (L4) is preserved untouched. One cleanup slice that retires the v2.17.0 shim half-life.
5
+ **PATCH bump from 3.0.1**. Two independent slices bundled into one release:
6
+ - **v2.19.0 change-id shim retirement** — full removal of the peaks-loop change-id axis (filesystem axis, code shim, envelope JSON slug, CLI flags). The change-id was a no-op post-v2.17.0 hard-kill; this slice retires the v2.17.0 shim half-life. OpenSpec's independent `openspec/changes/<change-id>/` vocabulary (L4) is preserved untouched.
7
+ - **3.0.2 Understand Anything hybrid context** — new `peaks understand context` subcommand that encapsulates the UA-first / codegraph-fallback / hybrid-union routing decision in the service layer (per the peaks-loop "skill-first / CLI-auxiliary" architecture). Consumers no longer pick one or the other at the LLM level.
8
+
9
+ The v2.19.0 work shipped as code commits (`f91d71c`, `5186d3d`, `b598612`, `ced835e`, `7ee9d8f`, plus follow-up tests) but was never formally released. v2.19.0 is therefore *not* a separate release line — its changes are rolled forward into 3.0.2. Future releases resume numeric sequence from 3.0.3.
6
10
 
7
11
  ### Removed — change-id shim (cleanup post v2.17.0 hard-kill)
8
12
 
@@ -70,7 +74,38 @@
70
74
  - Existing RD/QA artifacts in `.peaks/_runtime/<sid>/<role>/` remain readable; the planner no longer requires a change-id, and the change-id (if any) is embedded only as a filename slug.
71
75
  - The 2.8.3 hard-ban on `.peaks/_runtime/<YYYY-MM-DD-*>/` sibling directories is still enforced via the rewritten guard test and the inline `lstatSync` in `initWorkspace`.
72
76
 
73
- ## [2.18.4] — 2026-06-29 — peaks-solo first-run step gates: hard-pause 1.x→2.0 upgrade + make `--mode` optional on Step 1
77
+ ## [3.0.2] — 2026-07-02 — change-id shim retirement (v2.19.0) + Understand Anything hybrid context (3.0.2)
78
+
79
+ **PATCH bump from 3.0.1**. Two independent slices bundled into one release:
80
+ - **v2.19.0 change-id shim retirement** — full removal of the peaks-loop change-id axis (filesystem axis, code shim, envelope JSON slug, CLI flags). The change-id was a no-op post-v2.17.0 hard-kill; this slice retires the v2.17.0 shim half-life. OpenSpec's independent `openspec/changes/<change-id>/` vocabulary (L4) is preserved untouched.
81
+ - **3.0.2 Understand Anything hybrid context** — new `peaks understand context` subcommand that encapsulates the UA-first / codegraph-fallback / hybrid-union routing decision in the service layer (per the peaks-loop "skill-first / CLI-auxiliary" architecture). Consumers no longer pick one or the other at the LLM level.
82
+
83
+ The v2.19.0 work shipped as code commits (`f91d71c`, `5186d3d`, `b598612`, `ced835e`, `7ee9d8f`, plus follow-up tests) but was never formally released. v2.19.0 is therefore *not* a separate release line — its changes are rolled forward into 3.0.2. Future releases resume numeric sequence from 3.0.3.
84
+
85
+ ### Added — `peaks understand context` (3.0.2 user-facing feature)
86
+
87
+ The Understand Anything (UA) Claude Code plugin and `@colbymchenry/codegraph` are both complementary project-analysis tools. UA provides a high-level knowledge graph (tours, layers, expert prompts); codegraph provides low-level affected-file analysis. Until 3.0.2, peaks-loop consumers had to choose one or the other at the LLM level — there was no service-layer enforcement of either.
88
+
89
+ This slice adds a single `peaks understand context` subcommand that encapsulates the routing decision in the service layer (per the peaks-loop "skill-first / CLI-auxiliary" architecture — see `peaks-solo` SKILL.md). The envelope carries a `source` tag so downstream consumers can audit which evidence contributed to the context.
90
+
91
+ **Source routing** (deterministic, decided in `buildUnderstandContext`):
92
+ - `ua-only` — UA knowledge graph present, no codegraph
93
+ - `ua-missing-fallback-codegraph` — UA absent, codegraph produced evidence
94
+ - `ua-and-codegraph-hybrid` — both present, both contribute (parallel `Promise.all`)
95
+ - `both-missing` — neither produced evidence, exit code 2
96
+
97
+ **Added files (5):**
98
+ - `src/services/understand/understand-hybrid-service.ts` (133L, new): `buildUnderstandContext({projectRoot, files?, sampleSize?, artifactDir?, codegraphRunner?})` uses `Promise.all` to run UA scan and codegraph affected in parallel; total wall-clock = `max(uaMs, codegraphMs)`, not sum. Failures are caught into `warnings[]` (best-effort); the main envelope is always returned so consumers can audit missing evidence.
99
+ - `src/services/understand/hybrid-types.ts` (39L, new): `UnderstandContextResult` envelope + 4-value source enum + `CodegraphContextBlock`.
100
+ - `src/cli/commands/understand-commands.ts` (208L, +51L net): new `peaks understand context` subcommand at base path `admin/audit-logs` (the only 3rd CLI surface in the understand module family). The subcommand accepts `--project --files --sample --artifact-dir` and emits the envelope as JSON or human-readable summary.
101
+ - `tests/unit/understand-hybrid-service.test.ts` (110L, 4 cases): `ua-only` / `fallback` / `hybrid` / `both-missing`.
102
+ - `tests/integration/understand-hybrid-cli.test.ts` (85L, 3 cases): end-to-end CLI both-missing / ua-only / envelope-shape-stability.
103
+
104
+ **Test evidence:** 7/7 vitest (4 unit + 3 integration), `tsc --noEmit` clean.
105
+
106
+ The peak-loop skill-external-invocation audit pattern (capability discovery + references only + do-not-execute + Peaks authoritative) is preserved: codegraph is consumed via the existing `executeCodegraphInvocation` (no upstream installer calls); UA installation is a text `INSTALL_HINT` only.
107
+
108
+ ### Removed — change-id shim (cleanup post v2.17.0 hard-kill, rolled up from v2.19.0)
74
109
 
75
110
  **PATCH bump from 2.18.3**. Resolves the user-reported "新项目第一次使用 peaks-solo 时初始化完不按流程走、开发效果差、再开 session 后才正常" defect. Two surgical fixes to the first-run Solo gates (3 source files, 1 new test file, 2 test-regression updates; no schema change, no new dependencies, no public API change).
76
111
 
@@ -1,5 +1,6 @@
1
1
  import { InvalidArgumentError } from 'commander';
2
2
  import { scanUnderstandAnything, summarizeKnowledgeGraph } from '../../services/understand/understand-scan-service.js';
3
+ import { buildUnderstandContext } from '../../services/understand/understand-hybrid-service.js';
3
4
  import { fail, ok } from '../../shared/result.js';
4
5
  import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
5
6
  function parsePositiveInteger(value) {
@@ -109,4 +110,39 @@ export function registerUnderstandCommands(program, io) {
109
110
  process.exitCode = 1;
110
111
  }
111
112
  });
113
+ // Slice 2026-07-02-codegraph-ua-hybrid: hybrid context subcommand.
114
+ // Routes UA-first / codegraph-fallback / hybrid in the service layer;
115
+ // the CLI is a thin shell that surfaces the envelope and exit code.
116
+ addJsonOption(understand
117
+ .command('context')
118
+ .description('Build a hybrid project-context envelope: UA knowledge graph when present, codegraph affected as fallback, or both in parallel')
119
+ .requiredOption('--project <path>', 'target project root')
120
+ .option('--artifact-dir <path>', 'override the default .understand-anything directory')
121
+ .option('--sample <n>', 'maximum number of sample node ids to include (default 5)', parsePositiveInteger)
122
+ .option('--files <file...>', 'file globs to feed `codegraph affected` (default: src/index.ts, package.json, README.md)')).action(async (options) => {
123
+ try {
124
+ const ctxOptions = { projectRoot: options.project };
125
+ if (options.artifactDir !== undefined)
126
+ ctxOptions.artifactDir = options.artifactDir;
127
+ if (options.sample !== undefined)
128
+ ctxOptions.sampleSize = options.sample;
129
+ if (options.files !== undefined)
130
+ ctxOptions.files = options.files;
131
+ const result = await buildUnderstandContext(ctxOptions);
132
+ const exitCode = result.source === 'both-missing' ? 2 : 0;
133
+ if (exitCode !== 0) {
134
+ printResult(io, fail('understand.context', 'UNDERSTAND_CONTEXT_NO_EVIDENCE', `No UA graph and codegraph affected produced no usable evidence`, result, [
135
+ INSTALL_HINT,
136
+ 'Run `peaks codegraph index` in this project to populate the codegraph index, then retry'
137
+ ]), options.json);
138
+ process.exitCode = exitCode;
139
+ return;
140
+ }
141
+ printResult(io, ok('understand.context', result, [], result.warnings.length > 0 ? result.warnings : undefined), options.json);
142
+ }
143
+ catch (error) {
144
+ printResult(io, fail('understand.context', 'UNDERSTAND_CONTEXT_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Check the project path and codegraph installation before retrying']), options.json);
145
+ process.exitCode = 1;
146
+ }
147
+ });
112
148
  }
@@ -0,0 +1,32 @@
1
+ import type { CodegraphExecutionResult } from '../codegraph/codegraph-service.js';
2
+ import type { UnderstandScanReport } from './understand-types.js';
3
+ import type { UnderstandGraphSummary } from './understand-scan-service.js';
4
+ /**
5
+ * Slice 2026-07-02-codegraph-ua-hybrid — envelope contract for the
6
+ * `peaks understand context` command. The service layer decides which
7
+ * evidence to include based on environment detection; the source field
8
+ * is the authoritative routing signal.
9
+ */
10
+ export type UnderstandContextSource = 'ua-only' | 'ua-missing-fallback-codegraph' | 'ua-and-codegraph-hybrid' | 'both-missing';
11
+ export type CodegraphContextBlock = {
12
+ invocation: {
13
+ subcommand: 'affected';
14
+ files: string[];
15
+ };
16
+ execution: CodegraphExecutionResult;
17
+ /** Parsed JSON from `codegraph affected --json` when exitCode === 0; null otherwise. */
18
+ payload: unknown;
19
+ parseError?: string;
20
+ };
21
+ export type UnderstandContextResult = {
22
+ projectRoot: string;
23
+ source: UnderstandContextSource;
24
+ ua?: {
25
+ scan: UnderstandScanReport;
26
+ summary?: UnderstandGraphSummary;
27
+ };
28
+ codegraph?: CodegraphContextBlock;
29
+ /** Wall-clock milliseconds spent on the slowest parallel branch. */
30
+ durationMs: number;
31
+ warnings: string[];
32
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { type CodegraphProcessRunner } from '../codegraph/codegraph-service.js';
2
+ import type { UnderstandContextResult } from './hybrid-types.js';
3
+ export type BuildUnderstandContextOptions = {
4
+ projectRoot: string;
5
+ files?: string[];
6
+ sampleSize?: number;
7
+ artifactDir?: string;
8
+ codegraphRunner?: CodegraphProcessRunner;
9
+ };
10
+ export declare function buildUnderstandContext(options: BuildUnderstandContextOptions): Promise<UnderstandContextResult>;
@@ -0,0 +1,100 @@
1
+ import { createCodegraphInvocation, executeCodegraphInvocation } from '../codegraph/codegraph-service.js';
2
+ import { scanUnderstandAnything, summarizeKnowledgeGraph } from './understand-scan-service.js';
3
+ import { getErrorMessage } from '../../shared/result.js';
4
+ const DEFAULT_FILES = ['src/index.ts', 'package.json', 'README.md'];
5
+ function resolveFiles(files) {
6
+ if (files !== undefined && files.length > 0) {
7
+ return [...files];
8
+ }
9
+ return [...DEFAULT_FILES];
10
+ }
11
+ async function safeSummarize(options) {
12
+ try {
13
+ return { summary: await summarizeKnowledgeGraph(options) };
14
+ }
15
+ catch (error) {
16
+ return { warning: `UA summary failed: ${getErrorMessage(error)}` };
17
+ }
18
+ }
19
+ async function runCodegraph(projectRoot, files, runner) {
20
+ try {
21
+ const invocation = createCodegraphInvocation({
22
+ subcommand: 'affected',
23
+ project: projectRoot,
24
+ files,
25
+ json: true
26
+ });
27
+ const execution = await executeCodegraphInvocation(invocation, runner);
28
+ if (execution.exitCode !== 0) {
29
+ return { warning: `codegraph affected exited with code ${execution.exitCode}: ${execution.stderr.slice(0, 200)}` };
30
+ }
31
+ let payload = null;
32
+ let parseError;
33
+ try {
34
+ payload = JSON.parse(execution.stdout);
35
+ }
36
+ catch (error) {
37
+ parseError = getErrorMessage(error);
38
+ }
39
+ return {
40
+ block: {
41
+ invocation: { subcommand: 'affected', files },
42
+ execution,
43
+ payload,
44
+ ...(parseError !== undefined ? { parseError } : {})
45
+ }
46
+ };
47
+ }
48
+ catch (error) {
49
+ return { warning: `codegraph affected failed: ${getErrorMessage(error)}` };
50
+ }
51
+ }
52
+ function decideSource(uaScan, hasCodegraph) {
53
+ const uaPresent = uaScan.exists && uaScan.graph.exists && uaScan.graph.parseError === undefined;
54
+ if (uaPresent && hasCodegraph)
55
+ return 'ua-and-codegraph-hybrid';
56
+ if (uaPresent)
57
+ return 'ua-only';
58
+ if (hasCodegraph)
59
+ return 'ua-missing-fallback-codegraph';
60
+ return 'both-missing';
61
+ }
62
+ export async function buildUnderstandContext(options) {
63
+ const startedAt = Date.now();
64
+ const files = resolveFiles(options.files);
65
+ const sampleSize = options.sampleSize ?? 5;
66
+ const warnings = [];
67
+ const scanOptions = { projectRoot: options.projectRoot };
68
+ if (options.artifactDir !== undefined) {
69
+ scanOptions.artifactDir = options.artifactDir;
70
+ }
71
+ const uaScan = await scanUnderstandAnything(scanOptions);
72
+ const uaSummaryPromise = uaScan.exists && uaScan.graph.exists
73
+ ? safeSummarize({ projectRoot: options.projectRoot, sampleSize, ...(options.artifactDir !== undefined ? { artifactDir: options.artifactDir } : {}) })
74
+ : Promise.resolve({});
75
+ const [uaSummary, codegraphResult] = await Promise.all([
76
+ uaSummaryPromise,
77
+ runCodegraph(options.projectRoot, files, options.codegraphRunner)
78
+ ]);
79
+ if (uaSummary.warning)
80
+ warnings.push(uaSummary.warning);
81
+ if (codegraphResult.warning)
82
+ warnings.push(codegraphResult.warning);
83
+ const source = decideSource(uaScan, codegraphResult.block !== undefined);
84
+ const result = {
85
+ projectRoot: options.projectRoot,
86
+ source,
87
+ durationMs: Date.now() - startedAt,
88
+ warnings
89
+ };
90
+ if (source === 'ua-only' || source === 'ua-and-codegraph-hybrid') {
91
+ result.ua = {
92
+ scan: uaScan,
93
+ ...(uaSummary.summary !== undefined ? { summary: uaSummary.summary } : {})
94
+ };
95
+ }
96
+ if (codegraphResult.block) {
97
+ result.codegraph = codegraphResult.block;
98
+ }
99
+ return result;
100
+ }
@@ -1 +1 @@
1
- export declare const CLI_VERSION = "3.0.1";
1
+ export declare const CLI_VERSION = "3.0.2";
@@ -1 +1 @@
1
- export const CLI_VERSION = "3.0.1";
1
+ export const CLI_VERSION = "3.0.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-loop",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
5
5
  "author": "SquabbyZ",
6
6
  "license": "MIT",