peaks-cli 1.2.8 → 1.3.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/README.md +12 -0
- package/dist/src/cli/commands/project-commands.js +1 -1
- package/dist/src/cli/commands/scan-commands.js +22 -0
- package/dist/src/cli/commands/workspace-commands.js +59 -1
- package/dist/src/services/memory/project-memory-service.d.ts +1 -1
- package/dist/src/services/memory/project-memory-service.js +52 -23
- package/dist/src/services/sc/sc-service.d.ts +52 -1
- package/dist/src/services/sc/sc-service.js +266 -17
- package/dist/src/services/scan/libraries-service.d.ts +24 -0
- package/dist/src/services/scan/libraries-service.js +419 -0
- package/dist/src/services/scan/libraries-types.d.ts +59 -0
- package/dist/src/services/scan/libraries-types.js +9 -0
- package/dist/src/services/session/session-manager.d.ts +7 -5
- package/dist/src/services/session/session-manager.js +48 -14
- package/dist/src/services/skills/skill-presence-service.js +102 -68
- package/dist/src/services/skills/skill-runbook-service.js +36 -2
- package/dist/src/services/skills/skill-statusline-service.js +13 -7
- package/dist/src/services/workflow/autonomous-resume-writer.js +7 -7
- package/dist/src/services/workspace/reconcile-service.d.ts +119 -0
- package/dist/src/services/workspace/reconcile-service.js +464 -0
- package/dist/src/services/workspace/reconcile-types.d.ts +93 -0
- package/dist/src/services/workspace/reconcile-types.js +13 -0
- package/dist/src/shared/change-id.d.ts +30 -0
- package/dist/src/shared/change-id.js +40 -6
- package/dist/src/shared/paths.d.ts +1 -1
- package/dist/src/shared/paths.js +2 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +4 -1
- package/schemas/library-breaking-changes.data.json +141 -0
- package/schemas/library-breaking-changes.meta.json +6 -0
- package/schemas/library-breaking-changes.schema.json +50 -0
- package/skills/peaks-qa/SKILL.md +12 -0
- package/skills/peaks-rd/SKILL.md +145 -2
- package/skills/peaks-solo/SKILL.md +93 -319
- package/skills/peaks-solo/references/runbook.md +168 -0
- package/skills/peaks-solo/references/workflow-gates-and-types.md +177 -0
- package/skills/peaks-solo-resume/SKILL.md +81 -0
- package/skills/peaks-solo-status/SKILL.md +120 -0
- package/skills/peaks-solo-test/SKILL.md +84 -0
- package/skills/peaks-txt/SKILL.md +8 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
1
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
|
-
import { basename,
|
|
3
|
+
import { basename, join, resolve } from 'node:path';
|
|
4
4
|
import { isInsidePath } from '../../shared/path-utils.js';
|
|
5
5
|
import { getWorkspaceConfigForPath } from '../config/config-service.js';
|
|
6
6
|
import { getArtifactRemoteRepo, getArtifactWorkspaceStatus, getLocalArtifactPath } from '../artifacts/workspace-service.js';
|
|
@@ -21,6 +21,27 @@ const RETENTION_REQUIREMENTS = [
|
|
|
21
21
|
['sc', 'retention-boundary.md'],
|
|
22
22
|
['txt', 'context-capsule.md']
|
|
23
23
|
];
|
|
24
|
+
/**
|
|
25
|
+
* "Modern" retention requirements for the current peaks-cli artifact
|
|
26
|
+
* naming convention. The legacy `RETENTION_REQUIREMENTS` above assume
|
|
27
|
+
* older `refactor-goal.md` / `slice-spec.md` / `coverage-report.md` /
|
|
28
|
+
* `validation-report.md` / `change-impact.json` / `retention-boundary.md`
|
|
29
|
+
* / `context-capsule.md` filenames that predate the W4 session resolver.
|
|
30
|
+
*
|
|
31
|
+
* When the resolver finds a session that owns the slice, validate
|
|
32
|
+
* against the modern set: the actual files the current workflow emits
|
|
33
|
+
* (per-slice `prd/requests/<rid>.md`, per-slice `rd/requests/<rid>.md`,
|
|
34
|
+
* per-session `rd/tech-doc.md`, per-slice `qa/test-cases/<rid>.md`,
|
|
35
|
+
* per-slice `qa/test-reports/<rid>.md`, per-session `txt/handoff.md`).
|
|
36
|
+
* The legacy set is preserved for the workspace-artifact path so
|
|
37
|
+
* existing repos on the old convention keep working.
|
|
38
|
+
*/
|
|
39
|
+
const MODERN_RETENTION_REQUIREMENTS = [
|
|
40
|
+
'rd/tech-doc.md',
|
|
41
|
+
'qa/test-cases/{sliceId}.md',
|
|
42
|
+
'qa/test-reports/{sliceId}.md',
|
|
43
|
+
'txt/handoff.md'
|
|
44
|
+
];
|
|
24
45
|
const SLICE_ID_PATTERN = /^(?!\.{1,2}$)[A-Za-z0-9._-]+$/;
|
|
25
46
|
function getPeaksPath(workspaceRoot) {
|
|
26
47
|
return resolve(workspaceRoot, '.peaks');
|
|
@@ -108,6 +129,150 @@ function isRetainedArtifactFile(filePath, artifactWorkspacePath, changesRoot, ch
|
|
|
108
129
|
return false;
|
|
109
130
|
}
|
|
110
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Read the orchestrator's active-skill marker and return its
|
|
134
|
+
* `sessionId`, or null when the file is missing / malformed.
|
|
135
|
+
*
|
|
136
|
+
* As of slice 2026-06-05-peaks-runtime-layer the canonical home is
|
|
137
|
+
* `<projectRoot>/.peaks/_runtime/active-skill.json`. The legacy
|
|
138
|
+
* `<projectRoot>/.peaks/.active-skill.json` is consulted as a
|
|
139
|
+
* one-minor-release back-compat fallback: if the new path is
|
|
140
|
+
* absent but the legacy path is present and valid, we use the
|
|
141
|
+
* legacy value. The new path always wins when both exist.
|
|
142
|
+
*/
|
|
143
|
+
function readActiveSkillSessionId(projectRoot) {
|
|
144
|
+
const newPath = join(projectRoot, '.peaks', '_runtime', 'active-skill.json');
|
|
145
|
+
const legacyPath = join(projectRoot, '.peaks', '.active-skill.json');
|
|
146
|
+
const pathToRead = existsSync(newPath) ? newPath : legacyPath;
|
|
147
|
+
if (!existsSync(pathToRead))
|
|
148
|
+
return null;
|
|
149
|
+
try {
|
|
150
|
+
const raw = readFileSync(pathToRead, 'utf8');
|
|
151
|
+
const parsed = JSON.parse(raw);
|
|
152
|
+
if (typeof parsed?.sessionId === 'string' && parsed.sessionId.length > 0) {
|
|
153
|
+
return parsed.sessionId;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Read the workspace session binding and return its `sessionId`,
|
|
163
|
+
* or null when the file is missing / malformed.
|
|
164
|
+
*
|
|
165
|
+
* As of slice 2026-06-05-peaks-runtime-layer the canonical home is
|
|
166
|
+
* `<projectRoot>/.peaks/_runtime/session.json`. The legacy
|
|
167
|
+
* `<projectRoot>/.peaks/.session.json` is consulted as a
|
|
168
|
+
* one-minor-release back-compat fallback: if the new path is
|
|
169
|
+
* absent but the legacy path is present and valid, we use the
|
|
170
|
+
* legacy value. The new path always wins when both exist.
|
|
171
|
+
*/
|
|
172
|
+
function readSessionJsonBinding(projectRoot) {
|
|
173
|
+
const newPath = join(projectRoot, '.peaks', '_runtime', 'session.json');
|
|
174
|
+
const legacyPath = join(projectRoot, '.peaks', '.session.json');
|
|
175
|
+
const pathToRead = existsSync(newPath) ? newPath : legacyPath;
|
|
176
|
+
if (!existsSync(pathToRead))
|
|
177
|
+
return null;
|
|
178
|
+
try {
|
|
179
|
+
const raw = readFileSync(pathToRead, 'utf8');
|
|
180
|
+
const parsed = JSON.parse(raw);
|
|
181
|
+
if (typeof parsed?.sessionId === 'string' && parsed.sessionId.length > 0) {
|
|
182
|
+
return parsed.sessionId;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The "marker" artifact whose existence under a session dir is the signal
|
|
192
|
+
* that the session owns the slice. We look for `qa/test-cases/<sliceId>.md`
|
|
193
|
+
* first (a present test plan is the most decisive signal of session
|
|
194
|
+
* ownership for a slice id). If the test plan is absent we also accept
|
|
195
|
+
* `qa/test-reports/<sliceId>.md` (a finished QA report is also a decisive
|
|
196
|
+
* ownership signal). When neither exists for a candidate session, that
|
|
197
|
+
* session does not own the slice.
|
|
198
|
+
*/
|
|
199
|
+
function sessionOwnsSlice(projectRoot, sessionId, sliceId) {
|
|
200
|
+
const sessionDir = join(projectRoot, '.peaks', sessionId);
|
|
201
|
+
if (!existsSync(sessionDir))
|
|
202
|
+
return false;
|
|
203
|
+
for (const marker of [`qa/test-cases/${sliceId}.md`, `qa/test-reports/${sliceId}.md`]) {
|
|
204
|
+
if (existsSync(join(sessionDir, marker)))
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Find a session dir under `<projectRoot>/.peaks/` that owns the slice
|
|
211
|
+
* (see `sessionOwnsSlice`). Returns the first match in lexicographic
|
|
212
|
+
* order, or null when no session owns the slice.
|
|
213
|
+
*/
|
|
214
|
+
function findSessionOwningSlice(projectRoot, sliceId) {
|
|
215
|
+
const peaksRoot = join(projectRoot, '.peaks');
|
|
216
|
+
if (!existsSync(peaksRoot))
|
|
217
|
+
return null;
|
|
218
|
+
let names;
|
|
219
|
+
try {
|
|
220
|
+
names = readdirSync(peaksRoot);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
names.sort();
|
|
226
|
+
for (const name of names) {
|
|
227
|
+
if (!/^\d{4}-\d{2}-\d{2}-session-[a-f0-9]+$/.test(name))
|
|
228
|
+
continue;
|
|
229
|
+
if (sessionOwnsSlice(projectRoot, name, sliceId)) {
|
|
230
|
+
return name;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Resolve the session id that owns the slice's artifacts using a 3-tier
|
|
237
|
+
* precedence:
|
|
238
|
+
*
|
|
239
|
+
* 1. `.peaks/_runtime/active-skill.json` `sessionId` (with back-compat
|
|
240
|
+
* fallback to `.peaks/.active-skill.json`) if it points to a real
|
|
241
|
+
* session that owns the slice.
|
|
242
|
+
* 2. `.peaks/_runtime/session.json` `sessionId` (with back-compat
|
|
243
|
+
* fallback to `.peaks/.session.json`) if it points to a real
|
|
244
|
+
* session that owns the slice.
|
|
245
|
+
* 3. `find .peaks/ -name '<marker>'` — the first session dir under
|
|
246
|
+
* `.peaks/` that owns the slice.
|
|
247
|
+
* 4. else `{ resolvedSessionId: null, candidateSources: [] }`.
|
|
248
|
+
*
|
|
249
|
+
* The back-compat fallbacks are tolerated for one minor release so
|
|
250
|
+
* users with pre-migration trees (or running an older CLI version)
|
|
251
|
+
* still get a clean resolution. After the migration (or after v1.3.0
|
|
252
|
+
* is installed and `peaks workspace reconcile` has been run), only
|
|
253
|
+
* the new paths exist and the fallbacks never fire.
|
|
254
|
+
*
|
|
255
|
+
* `candidateSources` reports which sources were checked before the
|
|
256
|
+
* resolver found (or did not find) a winner; the list is in the order
|
|
257
|
+
* the resolver consulted them. This makes the precedence observable in
|
|
258
|
+
* the JSON envelope so a human reviewer can see "active-skill was empty
|
|
259
|
+
* AND session-json was empty, so find-fallback won".
|
|
260
|
+
*/
|
|
261
|
+
export function resolveArtifactSession(projectRoot, sliceId) {
|
|
262
|
+
const activeSkill = readActiveSkillSessionId(projectRoot);
|
|
263
|
+
if (activeSkill !== null && sessionOwnsSlice(projectRoot, activeSkill, sliceId)) {
|
|
264
|
+
return { resolvedSessionId: activeSkill, candidateSources: ['active-skill'] };
|
|
265
|
+
}
|
|
266
|
+
const sessionJson = readSessionJsonBinding(projectRoot);
|
|
267
|
+
if (sessionJson !== null && sessionOwnsSlice(projectRoot, sessionJson, sliceId)) {
|
|
268
|
+
return { resolvedSessionId: sessionJson, candidateSources: ['active-skill', 'session-json'] };
|
|
269
|
+
}
|
|
270
|
+
const findHit = findSessionOwningSlice(projectRoot, sliceId);
|
|
271
|
+
if (findHit !== null) {
|
|
272
|
+
return { resolvedSessionId: findHit, candidateSources: ['active-skill', 'session-json', 'find-fallback'] };
|
|
273
|
+
}
|
|
274
|
+
return { resolvedSessionId: null, candidateSources: [] };
|
|
275
|
+
}
|
|
111
276
|
export function getChangeTraceabilityStatus() {
|
|
112
277
|
const workspace = getWorkspaceConfigForPath(process.cwd());
|
|
113
278
|
const artifactStatus = getArtifactWorkspaceStatus(workspace?.workspaceId);
|
|
@@ -196,6 +361,8 @@ export function recordCommitBoundary(options) {
|
|
|
196
361
|
const workspace = getWorkspaceConfigForPath(process.cwd());
|
|
197
362
|
const artifactStatus = getArtifactWorkspaceStatus(workspace?.workspaceId);
|
|
198
363
|
const commitHash = getCurrentCommitHash(workspace?.rootPath);
|
|
364
|
+
const projectRoot = workspace?.rootPath ?? process.cwd();
|
|
365
|
+
const resolution = resolveArtifactSession(projectRoot, options.sliceId);
|
|
199
366
|
return {
|
|
200
367
|
sliceId: options.sliceId,
|
|
201
368
|
commitHash,
|
|
@@ -203,38 +370,120 @@ export function recordCommitBoundary(options) {
|
|
|
203
370
|
artifacts: options.artifacts ?? [],
|
|
204
371
|
codeFiles: options.codeFiles ?? [],
|
|
205
372
|
syncState: mapSyncState(artifactStatus.syncStatus),
|
|
206
|
-
rollbackPoint: commitHash
|
|
373
|
+
rollbackPoint: commitHash,
|
|
374
|
+
resolvedSessionId: resolution.resolvedSessionId,
|
|
375
|
+
candidateSources: resolution.candidateSources
|
|
207
376
|
};
|
|
208
377
|
}
|
|
209
378
|
export function validateArtifactRetention(sliceId) {
|
|
210
379
|
const workspace = getWorkspaceConfigForPath(process.cwd());
|
|
211
|
-
|
|
380
|
+
// Resolve from `process.cwd()` even when no workspace is configured, so
|
|
381
|
+
// the W4 session resolver can still find the slice's owning session.
|
|
382
|
+
// The legacy "no workspace" check still surfaces as a missing artifact,
|
|
383
|
+
// but the resolution happens first so the JSON envelope's additive
|
|
384
|
+
// `resolvedSessionId` is populated regardless of workspace state.
|
|
385
|
+
const projectRoot = workspace?.rootPath ?? process.cwd();
|
|
386
|
+
if (!SLICE_ID_PATTERN.test(sliceId)) {
|
|
212
387
|
return {
|
|
213
388
|
valid: false,
|
|
214
|
-
missingArtifacts: ['
|
|
215
|
-
warnings: ['
|
|
389
|
+
missingArtifacts: ['Invalid slice id'],
|
|
390
|
+
warnings: ['Slice id must stay inside .peaks/<session-id> and only contain letters, numbers, dots, underscores, or hyphens'],
|
|
391
|
+
resolvedSessionId: null,
|
|
392
|
+
candidateSources: []
|
|
216
393
|
};
|
|
217
394
|
}
|
|
218
|
-
|
|
395
|
+
const resolution = resolveArtifactSession(projectRoot, sliceId);
|
|
396
|
+
const effectiveSliceId = resolution.resolvedSessionId ?? sliceId;
|
|
397
|
+
// W4: if the resolver found a session, ALSO accept artifacts under
|
|
398
|
+
// `<projectRoot>/.peaks/<resolvedSessionId>/` (the canonical per-slice
|
|
399
|
+
// dir). The project-root peaks is where the orchestrator's skills
|
|
400
|
+
// actually write (see `initWorkspace` in `workspace-service.ts`), so
|
|
401
|
+
// when the resolution chain lands on a real session the artifacts are
|
|
402
|
+
// usually there. We accept either location — workspace artifact path
|
|
403
|
+
// OR project-root peaks — so the additive behavior does not regress
|
|
404
|
+
// existing workspaces.
|
|
405
|
+
const resolvedPeaksSessionDir = resolution.resolvedSessionId !== null
|
|
406
|
+
? join(projectRoot, '.peaks', resolution.resolvedSessionId)
|
|
407
|
+
: null;
|
|
408
|
+
// Collect present files: legacy workspace-artifact-path check, OR the
|
|
409
|
+
// resolved session's project-root peaks dir.
|
|
410
|
+
const legacyPresent = (folder, file) => {
|
|
411
|
+
if (!workspace)
|
|
412
|
+
return false;
|
|
413
|
+
const artifactWorkspacePath = getLocalArtifactPath(workspace);
|
|
414
|
+
const { peaksPath, changeDir } = getRetentionChangeDir(artifactWorkspacePath, effectiveSliceId);
|
|
415
|
+
const filePath = resolve(changeDir, folder, file);
|
|
416
|
+
return isRetainedArtifactFile(filePath, artifactWorkspacePath, peaksPath, changeDir);
|
|
417
|
+
};
|
|
418
|
+
const resolvedPresent = (folder, file) => {
|
|
419
|
+
if (resolvedPeaksSessionDir === null)
|
|
420
|
+
return false;
|
|
421
|
+
return existsSync(join(resolvedPeaksSessionDir, folder, file));
|
|
422
|
+
};
|
|
423
|
+
if (!workspace) {
|
|
424
|
+
// No workspace: validate against the resolved session dir directly
|
|
425
|
+
// (this is the common peaks-solo / peaks-rd invocation: the slice
|
|
426
|
+
// lives under the project-root `.peaks/<sessionId>/`, and the
|
|
427
|
+
// workspace artifact path is irrelevant). When the resolution also
|
|
428
|
+
// fails, fall back to the legacy "No workspace configured" failure
|
|
429
|
+
// mode so the existing CLI contract is preserved.
|
|
430
|
+
if (resolvedPeaksSessionDir === null) {
|
|
431
|
+
return {
|
|
432
|
+
valid: false,
|
|
433
|
+
missingArtifacts: ['No workspace configured'],
|
|
434
|
+
warnings: ['Cannot validate without a configured workspace'],
|
|
435
|
+
resolvedSessionId: resolution.resolvedSessionId,
|
|
436
|
+
candidateSources: resolution.candidateSources
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
const missingArtifacts = modernRequirementRelativePaths(sliceId).filter((rel) => !existsSync(join(resolvedPeaksSessionDir, rel)));
|
|
219
440
|
return {
|
|
220
|
-
valid:
|
|
221
|
-
missingArtifacts
|
|
222
|
-
warnings:
|
|
441
|
+
valid: missingArtifacts.length === 0,
|
|
442
|
+
missingArtifacts,
|
|
443
|
+
warnings: missingArtifacts.length === 0 ? [] : ['Some required artifact files are missing'],
|
|
444
|
+
resolvedSessionId: resolution.resolvedSessionId,
|
|
445
|
+
candidateSources: resolution.candidateSources
|
|
223
446
|
};
|
|
224
447
|
}
|
|
225
|
-
const artifactWorkspacePath = getLocalArtifactPath(workspace);
|
|
226
|
-
const { peaksPath, changeDir } = getRetentionChangeDir(artifactWorkspacePath, sliceId);
|
|
227
|
-
const changesRoot = peaksPath;
|
|
228
448
|
const missingArtifacts = RETENTION_REQUIREMENTS
|
|
229
|
-
.map(([folder, file]) =>
|
|
230
|
-
.filter((
|
|
231
|
-
|
|
449
|
+
.map(([folder, file]) => `${folder}/${file}`)
|
|
450
|
+
.filter((rel) => !legacyPresent(...rel.split('/')) && !resolvedPresent(...rel.split('/')));
|
|
451
|
+
// If the legacy check is short (i.e. we're missing a lot of legacy-named
|
|
452
|
+
// files) but the resolver landed on a real session, ALSO accept the
|
|
453
|
+
// modern set. The legacy set was designed for an older workflow naming
|
|
454
|
+
// and a freshly-minted session in the current peaks-cli flow will not
|
|
455
|
+
// have the legacy names. This keeps `peaks sc validate --slice-id <rid>`
|
|
456
|
+
// returning `valid: true` for slices that completed under the current
|
|
457
|
+
// peaks-cli convention.
|
|
458
|
+
if (missingArtifacts.length > 0 && resolvedPeaksSessionDir !== null) {
|
|
459
|
+
const modernMissing = modernRequirementRelativePaths(sliceId).filter((rel) => !resolvedPresent(...rel.split('/')));
|
|
460
|
+
if (modernMissing.length === 0) {
|
|
461
|
+
return {
|
|
462
|
+
valid: true,
|
|
463
|
+
missingArtifacts: [],
|
|
464
|
+
warnings: [],
|
|
465
|
+
resolvedSessionId: resolution.resolvedSessionId,
|
|
466
|
+
candidateSources: resolution.candidateSources
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
}
|
|
232
470
|
return {
|
|
233
471
|
valid: missingArtifacts.length === 0,
|
|
234
472
|
missingArtifacts,
|
|
235
|
-
warnings: missingArtifacts.length === 0 ? [] : ['Some required artifact files are missing']
|
|
473
|
+
warnings: missingArtifacts.length === 0 ? [] : ['Some required artifact files are missing'],
|
|
474
|
+
resolvedSessionId: resolution.resolvedSessionId,
|
|
475
|
+
candidateSources: resolution.candidateSources
|
|
236
476
|
};
|
|
237
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* Render the modern retention requirements as relative paths keyed
|
|
480
|
+
* against the slice id. The `{sliceId}` placeholder in the template
|
|
481
|
+
* is replaced with the actual slice id; per-session files (no
|
|
482
|
+
* placeholder) keep their literal name.
|
|
483
|
+
*/
|
|
484
|
+
function modernRequirementRelativePaths(sliceId) {
|
|
485
|
+
return MODERN_RETENTION_REQUIREMENTS.map((template) => template.replace('{sliceId}', sliceId));
|
|
486
|
+
}
|
|
238
487
|
export function getScHelpText() {
|
|
239
488
|
return [
|
|
240
489
|
'peaks sc status Show change traceability status',
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LibraryReport } from './libraries-types.js';
|
|
2
|
+
export type ScanLibrariesOptions = {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Parse the major version from a semver-ish spec.
|
|
7
|
+
*
|
|
8
|
+
* Handles the common shapes:
|
|
9
|
+
* "^5.18.0" → 5
|
|
10
|
+
* "~1.2.3" → 1
|
|
11
|
+
* "1.2.3" → 1
|
|
12
|
+
* ">=1.0.0" → 1
|
|
13
|
+
* "5" → 5
|
|
14
|
+
* "5.x" → 5
|
|
15
|
+
*
|
|
16
|
+
* Returns null for non-semver specs that the LLM should not assume a
|
|
17
|
+
* major for:
|
|
18
|
+
* "workspace:*" → null
|
|
19
|
+
* "file:../..." → null
|
|
20
|
+
* "git+https..." → null
|
|
21
|
+
* "npm:@scope/x@1" → 1 (alias spec, we extract what we can)
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseMajorVersion(spec: string): number | null;
|
|
24
|
+
export declare function scanLibraries(options: ScanLibrariesOptions): Promise<LibraryReport>;
|