peaks-cli 1.4.2 → 2.0.1
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 +279 -0
- package/README-en.md +226 -0
- package/README.md +152 -122
- 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/capability-commands.js +2 -1
- 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 +60 -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/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/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 +117 -2
- package/dist/src/cli/program.js +30 -0
- package/dist/src/lib/render/message-renderer.d.ts +20 -0
- package/dist/src/lib/render/message-renderer.js +80 -0
- 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 +111 -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 +36 -2
- package/dist/src/services/config/config-service.js +105 -0
- package/dist/src/services/config/config-types.d.ts +73 -0
- package/dist/src/services/config/config-types.js +28 -13
- package/dist/src/services/config/model-routing.js +5 -3
- package/dist/src/services/doctor/doctor-service.js +96 -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/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/rd/rd-service.js +29 -1
- 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 +86 -0
- package/dist/src/services/skills/sync-service.js +271 -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/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/workflow/workflow-router-service.js +15 -4
- package/dist/src/services/workspace/claude-settings-template.d.ts +53 -0
- package/dist/src/services/workspace/claude-settings-template.js +133 -0
- 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-service.d.ts +24 -0
- package/dist/src/services/workspace/workspace-service.js +124 -2
- 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 +8 -2
- 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 +16 -4
- package/skills/peaks-solo/references/anchoring-and-session-info.md +9 -0
- 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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, renameSync } from 'node:fs';
|
|
2
|
+
import { join, sep } from 'node:path';
|
|
3
|
+
import { assertValidSessionId } from './sid-naming-guard.js';
|
|
4
|
+
const ARCHIVE_ROOT = '_archive';
|
|
5
|
+
const RUNTIME_DIR = '_runtime';
|
|
6
|
+
function toPosix(p) {
|
|
7
|
+
return p.split(sep).join('/');
|
|
8
|
+
}
|
|
9
|
+
export function planArchive(projectRoot, sid) {
|
|
10
|
+
assertValidSessionId(sid);
|
|
11
|
+
const yyyyMm = sid.slice(0, 7);
|
|
12
|
+
const sourcePath = join(projectRoot, '.peaks', RUNTIME_DIR, sid);
|
|
13
|
+
const targetPath = join(projectRoot, '.peaks', ARCHIVE_ROOT, yyyyMm, sid);
|
|
14
|
+
return {
|
|
15
|
+
sid,
|
|
16
|
+
sourcePath: toPosix(sourcePath),
|
|
17
|
+
targetPath: toPosix(targetPath),
|
|
18
|
+
sourceExists: existsSync(sourcePath),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function archiveSession(projectRoot, options) {
|
|
22
|
+
const plan = planArchive(projectRoot, options.sid);
|
|
23
|
+
if (!plan.sourceExists) {
|
|
24
|
+
return { moved: [], skipped: [{ sid: options.sid, reason: 'source does not exist' }] };
|
|
25
|
+
}
|
|
26
|
+
if (!options.apply) {
|
|
27
|
+
return { moved: [], skipped: [{ sid: options.sid, reason: 'dry-run' }] };
|
|
28
|
+
}
|
|
29
|
+
mkdirSync(join(plan.targetPath, '..'), { recursive: true });
|
|
30
|
+
renameSync(plan.sourcePath, plan.targetPath);
|
|
31
|
+
return { moved: [options.sid], skipped: [] };
|
|
32
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface RuntimeSessionInfo {
|
|
2
|
+
sid: string;
|
|
3
|
+
mtimeMs: number;
|
|
4
|
+
ageHours: number;
|
|
5
|
+
}
|
|
6
|
+
export interface CleanupOptions {
|
|
7
|
+
olderThanHours: number;
|
|
8
|
+
graceHours: number;
|
|
9
|
+
}
|
|
10
|
+
export interface CleanupResult {
|
|
11
|
+
deleted: string[];
|
|
12
|
+
skipped: {
|
|
13
|
+
sid: string;
|
|
14
|
+
reason: string;
|
|
15
|
+
}[];
|
|
16
|
+
}
|
|
17
|
+
export declare function runtimeDirPath(projectRoot: string): string;
|
|
18
|
+
export declare function listRuntimeSessions(projectRoot: string): RuntimeSessionInfo[];
|
|
19
|
+
export declare function planRuntimeCleanup(sessions: RuntimeSessionInfo[], options: CleanupOptions): {
|
|
20
|
+
eligible: string[];
|
|
21
|
+
skipped: {
|
|
22
|
+
sid: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}[];
|
|
25
|
+
};
|
|
26
|
+
export declare function executeRuntimeCleanup(projectRoot: string, options: CleanupOptions & {
|
|
27
|
+
apply: boolean;
|
|
28
|
+
}): CleanupResult;
|
|
29
|
+
export interface SubAgentInvalidPlan {
|
|
30
|
+
invalid: string[];
|
|
31
|
+
invalidSidFormat: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function subAgentDirPath(projectRoot: string): string;
|
|
34
|
+
export declare function invalidSidsArchivePath(projectRoot: string): string;
|
|
35
|
+
export declare function listInvalidSubAgentSids(projectRoot: string): string[];
|
|
36
|
+
export declare function executeSubAgentClean(projectRoot: string, options: {
|
|
37
|
+
apply: boolean;
|
|
38
|
+
}): {
|
|
39
|
+
moved: string[];
|
|
40
|
+
skipped: string[];
|
|
41
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isBareSid, isValidSessionId } from './sid-naming-guard.js';
|
|
4
|
+
const RUNTIME_DIR = '_runtime';
|
|
5
|
+
export function runtimeDirPath(projectRoot) {
|
|
6
|
+
return join(projectRoot, '.peaks', RUNTIME_DIR);
|
|
7
|
+
}
|
|
8
|
+
export function listRuntimeSessions(projectRoot) {
|
|
9
|
+
const dir = runtimeDirPath(projectRoot);
|
|
10
|
+
if (!existsSync(dir))
|
|
11
|
+
return [];
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
14
|
+
.filter((e) => e.isDirectory())
|
|
15
|
+
.map((e) => {
|
|
16
|
+
const sid = e.name;
|
|
17
|
+
const fullPath = join(dir, sid);
|
|
18
|
+
const stat = statSync(fullPath);
|
|
19
|
+
const ageHours = (now - stat.mtimeMs) / (1000 * 3600);
|
|
20
|
+
return { sid, mtimeMs: stat.mtimeMs, ageHours };
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export function planRuntimeCleanup(sessions, options) {
|
|
24
|
+
const eligible = [];
|
|
25
|
+
const skipped = [];
|
|
26
|
+
const cutoffHours = options.olderThanHours + options.graceHours;
|
|
27
|
+
for (const s of sessions) {
|
|
28
|
+
if (s.ageHours >= cutoffHours) {
|
|
29
|
+
eligible.push(s.sid);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
skipped.push({ sid: s.sid, reason: `fresh: age=${s.ageHours.toFixed(1)}h < cutoff=${cutoffHours}h` });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { eligible, skipped };
|
|
36
|
+
}
|
|
37
|
+
export function executeRuntimeCleanup(projectRoot, options) {
|
|
38
|
+
const sessions = listRuntimeSessions(projectRoot);
|
|
39
|
+
const plan = planRuntimeCleanup(sessions, options);
|
|
40
|
+
if (options.apply) {
|
|
41
|
+
const dir = runtimeDirPath(projectRoot);
|
|
42
|
+
for (const sid of plan.eligible) {
|
|
43
|
+
rmSync(join(dir, sid), { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { deleted: plan.eligible, skipped: plan.skipped };
|
|
47
|
+
}
|
|
48
|
+
const SUBAGENT_DIR = '_sub_agents';
|
|
49
|
+
const INVALID_ARCHIVE = '_archive/invalid-sids';
|
|
50
|
+
export function subAgentDirPath(projectRoot) {
|
|
51
|
+
return join(projectRoot, '.peaks', SUBAGENT_DIR);
|
|
52
|
+
}
|
|
53
|
+
export function invalidSidsArchivePath(projectRoot) {
|
|
54
|
+
return join(projectRoot, '.peaks', INVALID_ARCHIVE);
|
|
55
|
+
}
|
|
56
|
+
export function listInvalidSubAgentSids(projectRoot) {
|
|
57
|
+
const dir = subAgentDirPath(projectRoot);
|
|
58
|
+
if (!existsSync(dir))
|
|
59
|
+
return [];
|
|
60
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
61
|
+
.filter((e) => e.isDirectory())
|
|
62
|
+
.map((e) => e.name)
|
|
63
|
+
.filter((name) => isBareSid(name) || !isValidSessionId(name));
|
|
64
|
+
}
|
|
65
|
+
export function executeSubAgentClean(projectRoot, options) {
|
|
66
|
+
const invalid = listInvalidSubAgentSids(projectRoot);
|
|
67
|
+
const moved = [];
|
|
68
|
+
if (options.apply && invalid.length > 0) {
|
|
69
|
+
const archiveDir = invalidSidsArchivePath(projectRoot);
|
|
70
|
+
mkdirSync(archiveDir, { recursive: true });
|
|
71
|
+
for (const sid of invalid) {
|
|
72
|
+
const from = join(subAgentDirPath(projectRoot), sid);
|
|
73
|
+
const to = join(archiveDir, sid);
|
|
74
|
+
if (existsSync(to)) {
|
|
75
|
+
// collision — append timestamp suffix
|
|
76
|
+
const stamped = `${sid}-${Date.now()}`;
|
|
77
|
+
renameSync(from, join(archiveDir, stamped));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
renameSync(from, to);
|
|
81
|
+
}
|
|
82
|
+
moved.push(sid);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { moved: options.apply ? moved : invalid, skipped: [] };
|
|
86
|
+
}
|
|
@@ -17,6 +17,14 @@ export type WorkspaceInitOptions = {
|
|
|
17
17
|
* (live sub-agent progress, spawn records).
|
|
18
18
|
*/
|
|
19
19
|
changeId?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Slice 2.0.1-bug3-fact-forcing-bypass: opt out of writing the
|
|
22
|
+
* consumer-project `.claude/settings.local.json` file. Default
|
|
23
|
+
* (`false`) writes the file so the [Fact-Forcing Gate] is bypassed
|
|
24
|
+
* for tool calls inside `.peaks/**`. The CLI surfaces this as
|
|
25
|
+
* `--no-claude-hooks`.
|
|
26
|
+
*/
|
|
27
|
+
noClaudeHooks?: boolean;
|
|
20
28
|
};
|
|
21
29
|
export type WorkspaceInitReport = {
|
|
22
30
|
sessionId: string;
|
|
@@ -27,6 +35,22 @@ export type WorkspaceInitReport = {
|
|
|
27
35
|
previousSessionId: string | null;
|
|
28
36
|
changeId: string | null;
|
|
29
37
|
changeIdAction: 'bound' | 'preserved' | 'none';
|
|
38
|
+
/**
|
|
39
|
+
* Slice 2.0.1-bug3-fact-forcing-bypass: what the consumer-project
|
|
40
|
+
* `.claude/settings.local.json` materialization did this call.
|
|
41
|
+
* - written: the file was freshly written
|
|
42
|
+
* - refreshed: the file already existed and was rewritten to
|
|
43
|
+
* match the current peaks-cli release's template
|
|
44
|
+
* - already-current: the file already matched the template; no
|
|
45
|
+
* rewrite needed
|
|
46
|
+
* - skipped: the caller passed noClaudeHooks=true
|
|
47
|
+
* The LLM and the user both see this in the JSON envelope so they
|
|
48
|
+
* can decide whether the bypass is in effect.
|
|
49
|
+
*/
|
|
50
|
+
claudeSettings: {
|
|
51
|
+
action: 'written' | 'refreshed' | 'already-current' | 'skipped';
|
|
52
|
+
path: string;
|
|
53
|
+
};
|
|
30
54
|
};
|
|
31
55
|
export declare class InvalidSessionIdError extends Error {
|
|
32
56
|
readonly code = "INVALID_SESSION_ID";
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { mkdir } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { isDirectory } from '../../shared/fs.js';
|
|
4
5
|
import { getSessionId, setCurrentSessionBinding, setSessionMeta } from '../session/session-manager.js';
|
|
5
6
|
import { setCurrentChangeId } from '../../shared/change-id.js';
|
|
7
|
+
import { buildClaudeSettingsLocalJson, CLAUDE_SETTINGS_LOCAL_FILENAME } from './claude-settings-template.js';
|
|
6
8
|
const SESSION_ID_PATTERN = /^\d{4}-\d{2}-\d{2}-[a-z][a-z0-9-]*[a-z0-9]$/;
|
|
7
9
|
const PROHIBITED_SUFFIXES = ['session', 'work', 'task', 'test', 'temp', 'tmp'];
|
|
8
10
|
// Auto-generated session ID pattern: YYYY-MM-DD-session-<6位hex>
|
|
@@ -173,6 +175,126 @@ export async function initWorkspace(options) {
|
|
|
173
175
|
bound,
|
|
174
176
|
previousSessionId,
|
|
175
177
|
changeId: resolvedChangeId,
|
|
176
|
-
changeIdAction
|
|
178
|
+
changeIdAction,
|
|
179
|
+
claudeSettings: await materializeClaudeSettingsLocal(options.projectRoot, options.noClaudeHooks === true)
|
|
177
180
|
};
|
|
178
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* The peaks-managed snippet appended to the consumer project's
|
|
184
|
+
* `.peaks/.gitignore` so the local-only settings file never lands
|
|
185
|
+
* in a commit. Marked with a managed-by header so we can detect (and
|
|
186
|
+
* not double-append) on subsequent inits.
|
|
187
|
+
*/
|
|
188
|
+
const PEAKS_GITIGNORE_HEADER = '# >>> peaks-cli managed snippet (slice 2.0.1-bug3) — do not edit by hand';
|
|
189
|
+
const PEAKS_GITIGNORE_FOOTER = '# <<< peaks-cli managed snippet';
|
|
190
|
+
const PEAKS_GITIGNORE_SNIPPET = [
|
|
191
|
+
PEAKS_GITIGNORE_HEADER,
|
|
192
|
+
'# Consumer-project .claude/settings.local.json: written by `peaks workspace init`',
|
|
193
|
+
'# to bypass Claude Code [Fact-Forcing Gate] for .peaks/** writes. Local-only.',
|
|
194
|
+
'.claude/settings.local.json',
|
|
195
|
+
PEAKS_GITIGNORE_FOOTER,
|
|
196
|
+
''
|
|
197
|
+
].join('\n');
|
|
198
|
+
/**
|
|
199
|
+
* Materialize the consumer-project `.claude/settings.local.json` and
|
|
200
|
+
* ensure the consumer's `.peaks/.gitignore` covers it. Returns a
|
|
201
|
+
* `claudeSettings` descriptor that the caller surfaces in the JSON
|
|
202
|
+
* envelope.
|
|
203
|
+
*
|
|
204
|
+
* The function is idempotent: re-running on an already-materialized
|
|
205
|
+
* project is a no-op (the file is rewritten only when its content
|
|
206
|
+
* diverges from the current peaks-cli release's template, which
|
|
207
|
+
* keeps the consumer up to date as the template evolves).
|
|
208
|
+
*
|
|
209
|
+
* Even when the caller passes `noClaudeHooks: true`, the function
|
|
210
|
+
* still writes a copy of the template at
|
|
211
|
+
* `.peaks/.claude-settings-template.json` so the user has an offline
|
|
212
|
+
* recovery path: copy the file contents into
|
|
213
|
+
* `.claude/settings.local.json` manually. The recovery path is
|
|
214
|
+
* documented in
|
|
215
|
+
* `skills/peaks-solo/references/anchoring-and-session-info.md`.
|
|
216
|
+
*/
|
|
217
|
+
async function materializeClaudeSettingsLocal(projectRoot, noClaudeHooks) {
|
|
218
|
+
const settingsRel = CLAUDE_SETTINGS_LOCAL_FILENAME;
|
|
219
|
+
const settingsPath = join(projectRoot, settingsRel);
|
|
220
|
+
const template = buildClaudeSettingsLocalJson();
|
|
221
|
+
const serialized = JSON.stringify(template, null, 2) + '\n';
|
|
222
|
+
// Always drop a copy of the template under .peaks/ so the
|
|
223
|
+
// --no-claude-hooks recovery flow has a known source-of-truth on
|
|
224
|
+
// disk. The file is gitignored by the snippet below.
|
|
225
|
+
await writeOfflineTemplateCopy(projectRoot, serialized);
|
|
226
|
+
if (noClaudeHooks) {
|
|
227
|
+
return { action: 'skipped', path: settingsRel };
|
|
228
|
+
}
|
|
229
|
+
// Best-effort: ensure .claude/ exists, then write the file. We do
|
|
230
|
+
// not assertSafeSettingsPath here (the .claude/ dir is local to
|
|
231
|
+
// the consumer and we trust it on first init; the existing
|
|
232
|
+
// hooks-settings-service applies the safety check for the Bash
|
|
233
|
+
// gate-enforce path).
|
|
234
|
+
await mkdir(join(projectRoot, '.claude'), { recursive: true });
|
|
235
|
+
let action = 'written';
|
|
236
|
+
if (existsSync(settingsPath)) {
|
|
237
|
+
try {
|
|
238
|
+
const { readFile } = await import('node:fs/promises');
|
|
239
|
+
const existing = await readFile(settingsPath, 'utf8');
|
|
240
|
+
if (existing === serialized) {
|
|
241
|
+
action = 'already-current';
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
action = 'refreshed';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// Treat any read failure as "needs refresh" so the consumer
|
|
249
|
+
// always ends up with a valid template on disk.
|
|
250
|
+
action = 'refreshed';
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (action !== 'already-current') {
|
|
254
|
+
await writeFile(settingsPath, serialized, 'utf8');
|
|
255
|
+
}
|
|
256
|
+
// Ensure the consumer's .peaks/.gitignore covers the local-only
|
|
257
|
+
// settings file. The snippet is appended only when the header is
|
|
258
|
+
// missing, so subsequent inits do not double-append.
|
|
259
|
+
await upsertPeaksGitignoreSnippet(projectRoot);
|
|
260
|
+
return { action, path: settingsRel };
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Always write (or refresh) a copy of the template at
|
|
264
|
+
* `.peaks/.claude-settings-template.json` so the user has a known
|
|
265
|
+
* source-of-truth on disk for the manual recovery flow. This file is
|
|
266
|
+
* tracked in git (not gitignored) because it is the recovery anchor
|
|
267
|
+
* — if the consumer needs to re-create their .claude/settings.local.json
|
|
268
|
+
* they can copy this file verbatim.
|
|
269
|
+
*/
|
|
270
|
+
async function writeOfflineTemplateCopy(projectRoot, serialized) {
|
|
271
|
+
const copyPath = join(projectRoot, '.peaks', '.claude-settings-template.json');
|
|
272
|
+
await mkdir(join(projectRoot, '.peaks'), { recursive: true });
|
|
273
|
+
await writeFile(copyPath, serialized, 'utf8');
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Append the peaks-managed `.claude/settings.local.json` snippet to
|
|
277
|
+
* the consumer project's `.peaks/.gitignore`. Preserves any user-
|
|
278
|
+
* managed entries above the snippet. Idempotent: re-running on a
|
|
279
|
+
* project that already has the snippet is a no-op.
|
|
280
|
+
*/
|
|
281
|
+
async function upsertPeaksGitignoreSnippet(projectRoot) {
|
|
282
|
+
const gitignorePath = join(projectRoot, '.peaks', '.gitignore');
|
|
283
|
+
await mkdir(join(projectRoot, '.peaks'), { recursive: true });
|
|
284
|
+
let existing = '';
|
|
285
|
+
if (existsSync(gitignorePath)) {
|
|
286
|
+
try {
|
|
287
|
+
const { readFile } = await import('node:fs/promises');
|
|
288
|
+
existing = await readFile(gitignorePath, 'utf8');
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
existing = '';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (existing.includes(PEAKS_GITIGNORE_HEADER)) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
|
|
298
|
+
const next = existing + separator + (existing.length > 0 ? '\n' : '') + PEAKS_GITIGNORE_SNIPPET;
|
|
299
|
+
await writeFile(gitignorePath, next, 'utf8');
|
|
300
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function isLegacyDecisionDotfile(name: string): boolean;
|
|
2
|
+
export declare function stateDirPath(projectRoot: string): string;
|
|
3
|
+
export interface CollectResult {
|
|
4
|
+
moved: string[];
|
|
5
|
+
skipped: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function collectLegacyDecisionDotfiles(projectRoot: string): CollectResult;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync } from 'node:fs';
|
|
2
|
+
import { join, posix } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Spec §8.4 + §8.5 — `.peaks/_state/` collects one-time decision dotfiles.
|
|
5
|
+
* Migrates from legacy `.peaks/<name>` flat layout.
|
|
6
|
+
*
|
|
7
|
+
* `stateDirPath` returns a POSIX-normalized logical path so callers and tests
|
|
8
|
+
* can compare against a platform-independent string (the .peaks tree is a
|
|
9
|
+
* config key, not a direct fs call). `mkdirSync` / `renameSync` below use
|
|
10
|
+
* the platform-native `join` so files actually land on disk.
|
|
11
|
+
*/
|
|
12
|
+
const LEGACY_DOTFILES = [
|
|
13
|
+
'.peaks-init-hooks-decision.json',
|
|
14
|
+
'.peaks-openspec-opt-in.json',
|
|
15
|
+
];
|
|
16
|
+
const STATE_DIR_NAME = '_state';
|
|
17
|
+
export function isLegacyDecisionDotfile(name) {
|
|
18
|
+
return LEGACY_DOTFILES.includes(name);
|
|
19
|
+
}
|
|
20
|
+
export function stateDirPath(projectRoot) {
|
|
21
|
+
return posix.join(projectRoot, '.peaks', STATE_DIR_NAME);
|
|
22
|
+
}
|
|
23
|
+
export function collectLegacyDecisionDotfiles(projectRoot) {
|
|
24
|
+
const peaksDir = join(projectRoot, '.peaks');
|
|
25
|
+
// Use the platform-native join for actual filesystem operations;
|
|
26
|
+
// `stateDirPath` (POSIX) is only the public/portable surface for callers.
|
|
27
|
+
const stateDir = join(projectRoot, '.peaks', STATE_DIR_NAME);
|
|
28
|
+
mkdirSync(stateDir, { recursive: true });
|
|
29
|
+
const moved = [];
|
|
30
|
+
const skipped = [];
|
|
31
|
+
for (const name of LEGACY_DOTFILES) {
|
|
32
|
+
const from = join(peaksDir, name);
|
|
33
|
+
const to = join(stateDir, name);
|
|
34
|
+
if (!existsSync(from))
|
|
35
|
+
continue;
|
|
36
|
+
if (existsSync(to)) {
|
|
37
|
+
throw new Error(`DOTFILE_COLLISION: ${name} already exists in ${stateDir} (${readFileSync(to, 'utf8').length} bytes); refusing to overwrite`);
|
|
38
|
+
}
|
|
39
|
+
renameSync(from, to);
|
|
40
|
+
moved.push(name);
|
|
41
|
+
}
|
|
42
|
+
return { moved, skipped };
|
|
43
|
+
}
|
|
@@ -303,7 +303,10 @@ export function setCurrentChangeId(projectRoot, changeId, options = {}) {
|
|
|
303
303
|
catch { /* best effort */ }
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
|
-
|
|
306
|
+
// On Windows, use a 'junction' (directory hard link) which doesn't
|
|
307
|
+
// require developer mode / admin. POSIX uses a regular 'dir' symlink.
|
|
308
|
+
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
309
|
+
symlinkSync(targetDir, bindingPath, linkType);
|
|
307
310
|
}
|
|
308
311
|
/**
|
|
309
312
|
* Canonical on-disk path to a change-id's reviewable artifacts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "
|
|
1
|
+
export declare const CLI_VERSION = "2.0.1";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "
|
|
1
|
+
export const CLI_VERSION = "2.0.1";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peaks-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
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",
|
|
@@ -33,7 +33,12 @@
|
|
|
33
33
|
"!skills/**/test-prompts.json",
|
|
34
34
|
"!skills/**/.DS_Store",
|
|
35
35
|
"output-styles/**",
|
|
36
|
-
"schemas/*.json"
|
|
36
|
+
"schemas/*.json",
|
|
37
|
+
".claude-plugin/**",
|
|
38
|
+
"README.md",
|
|
39
|
+
"README-en.md",
|
|
40
|
+
"CHANGELOG.md",
|
|
41
|
+
"LICENSE"
|
|
37
42
|
],
|
|
38
43
|
"scripts": {
|
|
39
44
|
"build": "node ./scripts/sync-version.mjs && node ./scripts/clean-dist.mjs && tsc -p tsconfig.json",
|
|
@@ -54,6 +59,7 @@
|
|
|
54
59
|
"node": ">=20.0.0"
|
|
55
60
|
},
|
|
56
61
|
"dependencies": {
|
|
62
|
+
"@alibaba-group/open-code-review": "1.3.1",
|
|
57
63
|
"@colbymchenry/codegraph": "0.7.10",
|
|
58
64
|
"commander": "^12.1.0",
|
|
59
65
|
"fzf": "^0.5.2",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"properties": {
|
|
14
14
|
"id": {
|
|
15
15
|
"type": "string",
|
|
16
|
-
"pattern": "^(skill|skill-name|skill-parse|skill-runbook|skill-apply-note|skill-presence|statusline|schema|config|doctor-self|capability|build|integration):[A-Za-z0-9][A-Za-z0-9._-]*$",
|
|
16
|
+
"pattern": "^(skill|skill-name|skill-parse|skill-runbook|skill-apply-note|skill-presence|statusline|schema|config|doctor-self|capability|build|integration|L3):[A-Za-z0-9][A-Za-z0-9._-]*$",
|
|
17
17
|
"description": "Stable check id. Known prefixes: skill:<name> (required skill present), skill-name:<dir> (directory matches declared name), skill-parse:<dir> (skill metadata parsed), skill-runbook:<name> (Default runbook section exists), skill-apply-note:<name> (destructive --apply lines carry an authorization/--dry-run note), skill-presence:<topic> (status of .peaks/.active-skill.json — current/freshness/workspace), statusline:<topic> (out-of-band Claude Code statusLine — install/runtime), schema:<file> (schema file exists and is valid JSON), config:<scope> (optional config locations), doctor-self:<topic> (doctor validates its own output against this schema), capability:<name> (third-party capability is resolvable at the pinned version), build:<topic> (build-hygiene checks — dist/source version consistency), integration:<name> (third-party integration probe — e.g. gateguard-fact-force PreToolUse hook conflict on .peaks/**)."
|
|
18
18
|
},
|
|
19
19
|
"ok": { "type": "boolean" },
|