peaks-cli 1.4.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +51 -0
- package/CHANGELOG.md +238 -0
- package/README-en.md +226 -0
- package/README.md +142 -165
- package/dist/src/cli/commands/agent-commands.d.ts +20 -0
- package/dist/src/cli/commands/agent-commands.js +48 -0
- package/dist/src/cli/commands/audit-commands.d.ts +18 -0
- package/dist/src/cli/commands/audit-commands.js +138 -0
- package/dist/src/cli/commands/classify-classify-commands.d.ts +19 -0
- package/dist/src/cli/commands/classify-classify-commands.js +151 -0
- package/dist/src/cli/commands/code-review-commands.d.ts +34 -0
- package/dist/src/cli/commands/code-review-commands.js +83 -0
- package/dist/src/cli/commands/config-commands.js +90 -0
- package/dist/src/cli/commands/context-commands.d.ts +21 -0
- package/dist/src/cli/commands/context-commands.js +167 -0
- package/dist/src/cli/commands/core-artifact-commands.js +81 -2
- package/dist/src/cli/commands/hook-handle.js +50 -0
- package/dist/src/cli/commands/loop-commands.d.ts +21 -0
- package/dist/src/cli/commands/loop-commands.js +128 -0
- package/dist/src/cli/commands/memory-commands.d.ts +13 -0
- package/dist/src/cli/commands/memory-commands.js +60 -0
- package/dist/src/cli/commands/openspec-commands.js +37 -0
- package/dist/src/cli/commands/preferences-commands.d.ts +2 -0
- package/dist/src/cli/commands/preferences-commands.js +147 -0
- package/dist/src/cli/commands/retrospective-commands.d.ts +9 -0
- package/dist/src/cli/commands/retrospective-commands.js +58 -0
- package/dist/src/cli/commands/skill-conformance-commands.d.ts +9 -0
- package/dist/src/cli/commands/skill-conformance-commands.js +39 -0
- package/dist/src/cli/commands/understand-commands.js +34 -0
- package/dist/src/cli/commands/upgrade-commands.d.ts +23 -0
- package/dist/src/cli/commands/upgrade-commands.js +57 -0
- package/dist/src/cli/commands/workflow-commands.js +70 -0
- package/dist/src/cli/commands/workspace-commands.js +86 -0
- package/dist/src/cli/program.js +46 -22
- package/dist/src/services/agent/ecc-agent-service.d.ts +47 -0
- package/dist/src/services/agent/ecc-agent-service.js +143 -0
- package/dist/src/services/artifacts/request-artifact-service.js +14 -0
- package/dist/src/services/audit/backing-detector.d.ts +24 -0
- package/dist/src/services/audit/backing-detector.js +59 -0
- package/dist/src/services/audit/classifier.d.ts +38 -0
- package/dist/src/services/audit/classifier.js +127 -0
- package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +29 -0
- package/dist/src/services/audit/enforcers/active-skill-resolver.js +71 -0
- package/dist/src/services/audit/enforcers/design-draft-confirm.d.ts +25 -0
- package/dist/src/services/audit/enforcers/design-draft-confirm.js +54 -0
- package/dist/src/services/audit/enforcers/lint-audit-regression.d.ts +21 -0
- package/dist/src/services/audit/enforcers/lint-audit-regression.js +86 -0
- package/dist/src/services/audit/enforcers/lint-catalog-governance.d.ts +27 -0
- package/dist/src/services/audit/enforcers/lint-catalog-governance.js +38 -0
- package/dist/src/services/audit/enforcers/lint-cli-back.d.ts +16 -0
- package/dist/src/services/audit/enforcers/lint-cli-back.js +35 -0
- package/dist/src/services/audit/enforcers/lint-output-style.d.ts +11 -0
- package/dist/src/services/audit/enforcers/lint-output-style.js +94 -0
- package/dist/src/services/audit/enforcers/lint-reference-integrity.d.ts +6 -0
- package/dist/src/services/audit/enforcers/lint-reference-integrity.js +83 -0
- package/dist/src/services/audit/enforcers/lint-reference-shape.d.ts +30 -0
- package/dist/src/services/audit/enforcers/lint-reference-shape.js +272 -0
- package/dist/src/services/audit/enforcers/lint-style.d.ts +49 -0
- package/dist/src/services/audit/enforcers/lint-style.js +173 -0
- package/dist/src/services/audit/enforcers/lint-workflow-shape.d.ts +5 -0
- package/dist/src/services/audit/enforcers/lint-workflow-shape.js +141 -0
- package/dist/src/services/audit/enforcers/login-gate.d.ts +23 -0
- package/dist/src/services/audit/enforcers/login-gate.js +40 -0
- package/dist/src/services/audit/enforcers/mock-placement.d.ts +25 -0
- package/dist/src/services/audit/enforcers/mock-placement.js +48 -0
- package/dist/src/services/audit/enforcers/no-root-pollution.d.ts +21 -0
- package/dist/src/services/audit/enforcers/no-root-pollution.js +56 -0
- package/dist/src/services/audit/enforcers/pre-rd-scan.d.ts +22 -0
- package/dist/src/services/audit/enforcers/pre-rd-scan.js +23 -0
- package/dist/src/services/audit/enforcers/prototype-fidelity.d.ts +25 -0
- package/dist/src/services/audit/enforcers/prototype-fidelity.js +75 -0
- package/dist/src/services/audit/enforcers/resume-detection.d.ts +21 -0
- package/dist/src/services/audit/enforcers/resume-detection.js +52 -0
- package/dist/src/services/audit/enforcers/solo-code-ban.d.ts +23 -0
- package/dist/src/services/audit/enforcers/solo-code-ban.js +27 -0
- package/dist/src/services/audit/enforcers/sub-agent-sid.d.ts +25 -0
- package/dist/src/services/audit/enforcers/sub-agent-sid.js +63 -0
- package/dist/src/services/audit/enforcers/tech-doc-presence.d.ts +28 -0
- package/dist/src/services/audit/enforcers/tech-doc-presence.js +35 -0
- package/dist/src/services/audit/red-line-catalog-p2-a.d.ts +21 -0
- package/dist/src/services/audit/red-line-catalog-p2-a.js +233 -0
- package/dist/src/services/audit/red-line-catalog-p2-b.d.ts +19 -0
- package/dist/src/services/audit/red-line-catalog-p2-b.js +225 -0
- package/dist/src/services/audit/red-line-catalog.d.ts +51 -0
- package/dist/src/services/audit/red-line-catalog.js +210 -0
- package/dist/src/services/audit/red-lines-service.d.ts +23 -0
- package/dist/src/services/audit/red-lines-service.js +486 -0
- package/dist/src/services/audit/scanners/openspec-scanner.d.ts +15 -0
- package/dist/src/services/audit/scanners/openspec-scanner.js +55 -0
- package/dist/src/services/audit/scanners/rules-tree-scanner.d.ts +16 -0
- package/dist/src/services/audit/scanners/rules-tree-scanner.js +56 -0
- package/dist/src/services/audit/scanners/skills-tree-scanner.d.ts +17 -0
- package/dist/src/services/audit/scanners/skills-tree-scanner.js +46 -0
- package/dist/src/services/audit/static-service.d.ts +57 -0
- package/dist/src/services/audit/static-service.js +125 -0
- package/dist/src/services/audit/types.d.ts +69 -0
- package/dist/src/services/audit/types.js +13 -0
- package/dist/src/services/classify/classify-service.d.ts +42 -0
- package/dist/src/services/classify/classify-service.js +122 -0
- package/dist/src/services/classify/classify-types.d.ts +79 -0
- package/dist/src/services/classify/classify-types.js +90 -0
- package/dist/src/services/code-review/ocr-service.d.ts +129 -0
- package/dist/src/services/code-review/ocr-service.js +362 -0
- package/dist/src/services/config/config-migration.d.ts +32 -0
- package/dist/src/services/config/config-migration.js +92 -0
- package/dist/src/services/config/config-restore.d.ts +10 -0
- package/dist/src/services/config/config-restore.js +47 -0
- package/dist/src/services/config/config-rollback.d.ts +13 -0
- package/dist/src/services/config/config-rollback.js +26 -0
- package/dist/src/services/config/config-service.d.ts +35 -2
- package/dist/src/services/config/config-service.js +81 -0
- package/dist/src/services/config/config-types.d.ts +58 -0
- package/dist/src/services/config/config-types.js +6 -0
- package/dist/src/services/doctor/doctor-service.js +96 -0
- package/dist/src/services/fuzzy-matching/fuzzy-match-service.d.ts +15 -0
- package/dist/src/services/fuzzy-matching/fuzzy-match-service.js +56 -0
- package/dist/src/services/fuzzy-matching/types.d.ts +20 -0
- package/dist/src/services/fuzzy-matching/types.js +1 -0
- package/dist/src/services/ide/adapters/hermes-adapter.d.ts +21 -0
- package/dist/src/services/ide/adapters/hermes-adapter.js +51 -0
- package/dist/src/services/ide/adapters/openclaw-adapter.d.ts +14 -0
- package/dist/src/services/ide/adapters/openclaw-adapter.js +42 -0
- package/dist/src/services/ide/ide-registry.js +7 -0
- package/dist/src/services/ide/ide-types.d.ts +1 -1
- package/dist/src/services/memory/memory-search-service.d.ts +61 -0
- package/dist/src/services/memory/memory-search-service.js +80 -0
- package/dist/src/services/openspec/openspec-propose-from-doctor-service.d.ts +31 -0
- package/dist/src/services/openspec/openspec-propose-from-doctor-service.js +95 -0
- package/dist/src/services/preferences/preferences-service.d.ts +6 -0
- package/dist/src/services/preferences/preferences-service.js +43 -0
- package/dist/src/services/preferences/preferences-types.d.ts +90 -0
- package/dist/src/services/preferences/preferences-types.js +38 -0
- package/dist/src/services/recommendations/capability-seed-items.js +0 -1
- package/dist/src/services/recommendations/capability-seed-mappings.js +0 -1
- package/dist/src/services/recommendations/capability-seed-sources.js +0 -1
- package/dist/src/services/retrospective/retrospective-search-service.d.ts +37 -0
- package/dist/src/services/retrospective/retrospective-search-service.js +75 -0
- package/dist/src/services/skills/skill-conformance-service.d.ts +40 -0
- package/dist/src/services/skills/skill-conformance-service.js +136 -0
- package/dist/src/services/skills/skill-runbook-service.js +44 -10
- package/dist/src/services/skills/sync-service.d.ts +43 -0
- package/dist/src/services/skills/sync-service.js +99 -0
- package/dist/src/services/slice/slice-check-service.js +166 -13
- package/dist/src/services/slice/slice-check-types.d.ts +1 -1
- package/dist/src/services/standards/migrate-claude-rules-service.d.ts +19 -0
- package/dist/src/services/standards/migrate-claude-rules-service.js +193 -0
- package/dist/src/services/standards/project-context.d.ts +1 -1
- package/dist/src/services/standards/project-context.js +0 -4
- package/dist/src/services/standards/project-standards-service.js +1 -3
- package/dist/src/services/understand/understand-scan-service.js +15 -2
- package/dist/src/services/understand/understand-types.d.ts +26 -0
- package/dist/src/services/upgrade/1x-detector-service.d.ts +7 -0
- package/dist/src/services/upgrade/1x-detector-service.js +94 -0
- package/dist/src/services/upgrade/gitignore-migrate-service.d.ts +56 -0
- package/dist/src/services/upgrade/gitignore-migrate-service.js +170 -0
- package/dist/src/services/upgrade/upgrade-service.d.ts +47 -0
- package/dist/src/services/upgrade/upgrade-service.js +381 -0
- package/dist/src/services/workspace/migrate-1-4-1-service.js +1 -1
- package/dist/src/services/workspace/sid-naming-guard.d.ts +14 -0
- package/dist/src/services/workspace/sid-naming-guard.js +31 -0
- package/dist/src/services/workspace/workspace-archive-service.d.ts +19 -0
- package/dist/src/services/workspace/workspace-archive-service.js +32 -0
- package/dist/src/services/workspace/workspace-clean-service.d.ts +41 -0
- package/dist/src/services/workspace/workspace-clean-service.js +86 -0
- package/dist/src/services/workspace/workspace-state-service.d.ts +7 -0
- package/dist/src/services/workspace/workspace-state-service.js +43 -0
- package/dist/src/shared/change-id.js +4 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +10 -8
- package/schemas/doctor-report.schema.json +1 -1
- package/scripts/install-skills.mjs +296 -12
- package/skills/peaks-doctor/SKILL.md +59 -0
- package/skills/peaks-doctor/references/doctor-check-catalog.md +31 -0
- package/skills/peaks-doctor/references/from-doctor-flow.md +64 -0
- package/skills/peaks-doctor/test_prompts.json +17 -0
- package/skills/peaks-ide/SKILL.md +2 -0
- package/skills/peaks-qa/SKILL.md +9 -7
- package/skills/peaks-qa/references/artifact-per-request.md +19 -5
- package/skills/peaks-qa/references/qa-perf-test-plan.md +6 -6
- package/skills/peaks-qa/references/qa-runbook.md +1 -1
- package/skills/peaks-rd/SKILL.md +25 -10
- package/skills/peaks-rd/references/ocr-integration.md +214 -0
- package/skills/peaks-rd/references/rd-fanout-contracts.md +70 -0
- package/skills/peaks-rd/references/rd-runbook.md +1 -1
- package/skills/peaks-solo/SKILL.md +11 -5
- package/skills/peaks-solo/references/completion-handoff.md +3 -1
- package/skills/peaks-solo/references/step-0-55-1x-detection.md +82 -0
- package/skills/peaks-solo/references/workflow-gates-and-types.md +9 -0
- package/dist/src/cli/commands/shadcn-commands.d.ts +0 -3
- package/dist/src/cli/commands/shadcn-commands.js +0 -35
- package/dist/src/cli/commands/skill-context-stats-command.d.ts +0 -40
- package/dist/src/cli/commands/skill-context-stats-command.js +0 -96
- package/dist/src/cli/commands/skill-scope-commands.d.ts +0 -51
- package/dist/src/cli/commands/skill-scope-commands.js +0 -310
- package/dist/src/services/shadcn/shadcn-service.d.ts +0 -27
- package/dist/src/services/shadcn/shadcn-service.js +0 -128
- package/dist/src/services/skill-scope/adapters/_stub-helper.d.ts +0 -39
- package/dist/src/services/skill-scope/adapters/_stub-helper.js +0 -98
- package/dist/src/services/skill-scope/adapters/claude-code.d.ts +0 -59
- package/dist/src/services/skill-scope/adapters/claude-code.js +0 -304
- package/dist/src/services/skill-scope/adapters/codex.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/codex.js +0 -12
- package/dist/src/services/skill-scope/adapters/cursor.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/cursor.js +0 -13
- package/dist/src/services/skill-scope/adapters/qoder.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/qoder.js +0 -13
- package/dist/src/services/skill-scope/adapters/tongyi.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/tongyi.js +0 -13
- package/dist/src/services/skill-scope/adapters/trae.d.ts +0 -2
- package/dist/src/services/skill-scope/adapters/trae.js +0 -12
- package/dist/src/services/skill-scope/detect.d.ts +0 -81
- package/dist/src/services/skill-scope/detect.js +0 -513
- package/dist/src/services/skill-scope/registry.d.ts +0 -41
- package/dist/src/services/skill-scope/registry.js +0 -83
- package/dist/src/services/skill-scope/source-of-truth.d.ts +0 -44
- package/dist/src/services/skill-scope/source-of-truth.js +0 -118
- package/dist/src/services/skill-scope/types.d.ts +0 -195
- package/dist/src/services/skill-scope/types.js +0 -97
|
@@ -6,10 +6,12 @@ import { executeProjectMemoryBackup, executeProjectMemoryExtract, summarizeProje
|
|
|
6
6
|
import { summarizeProjectStandardsInitResult, summarizeProjectStandardsUpdateResult } from '../../services/standards/project-standards-service.js';
|
|
7
7
|
import { executeProjectStandardsInitIdeAware, executeProjectStandardsUpdateIdeAware } from '../../services/standards/ide-aware-standards-service.js';
|
|
8
8
|
import { migrateStandards } from '../../services/standards/migrate-service.js';
|
|
9
|
+
import { migrateClaudeRules } from '../../services/standards/migrate-claude-rules-service.js';
|
|
9
10
|
import { listProfiles } from '../../services/profiles/profile-service.js';
|
|
10
11
|
import { planProxyTest } from '../../services/proxy/proxy-service.js';
|
|
11
12
|
import { runDoctor } from '../../services/doctor/doctor-service.js';
|
|
12
13
|
import { listSkills } from '../../services/skills/skill-registry.js';
|
|
14
|
+
import { runSkillSync, SYNC_PLATFORMS } from '../../services/skills/sync-service.js';
|
|
13
15
|
import { inspectSkillRunbook } from '../../services/skills/skill-runbook-service.js';
|
|
14
16
|
import { setSkillPresence, clearSkillPresence, getSkillPresence, isSkillPresenceMode, touchSkillHeartbeat } from '../../services/skills/skill-presence-service.js';
|
|
15
17
|
import { detectPresenceMarker } from '../../services/hooks/presence-marker-detector.js';
|
|
@@ -88,6 +90,42 @@ export function registerCoreAndArtifactCommands(program, io) {
|
|
|
88
90
|
process.exitCode = 1;
|
|
89
91
|
}
|
|
90
92
|
});
|
|
93
|
+
// Slice #12 final piece (per spec §9 line 1105):
|
|
94
|
+
// `peaks skills sync 8 平台分发`. Idempotent: re-running is a
|
|
95
|
+
// no-op when the symlinks are already correct.
|
|
96
|
+
addJsonOption(skill
|
|
97
|
+
.command('sync')
|
|
98
|
+
.description(`Sync the peaks-* skill family to one or all of the 8 supported LLM-CLI platforms (${SYNC_PLATFORMS.join(', ')}). Idempotent.`)
|
|
99
|
+
.option('--platform <id>', `sync only one platform (default: --all). Valid: ${SYNC_PLATFORMS.join(', ')}`)
|
|
100
|
+
.option('--all', 'sync all 8 platforms (default if --platform is omitted)')
|
|
101
|
+
.option('--dry-run', 'do not write; emit the same shape with applied=false')
|
|
102
|
+
.option('--project <path>', 'project root (default: cwd)')).action(async (options) => {
|
|
103
|
+
try {
|
|
104
|
+
const projectRoot = options.project ?? process.cwd();
|
|
105
|
+
const platforms = options.platform !== undefined ? [options.platform] : undefined;
|
|
106
|
+
const result = await runSkillSync({
|
|
107
|
+
projectRoot,
|
|
108
|
+
...(platforms !== undefined ? { platforms } : {}),
|
|
109
|
+
...(options.dryRun === true ? { dryRun: true } : {}),
|
|
110
|
+
});
|
|
111
|
+
const envelope = ok('skill.sync', result, [], [
|
|
112
|
+
`syncedCount: ${result.syncedCount}/${result.perPlatform.length} platforms`,
|
|
113
|
+
`totalInstalled: ${result.totalInstalled} skill symlinks`,
|
|
114
|
+
result.failedCount > 0
|
|
115
|
+
? `failedCount: ${result.failedCount} (run \`peaks skill status\` for details)`
|
|
116
|
+
: 'no failures',
|
|
117
|
+
]);
|
|
118
|
+
printResult(io, envelope, options.json);
|
|
119
|
+
if (result.failedCount > 0) {
|
|
120
|
+
process.exitCode = 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
const message = getErrorMessage(error);
|
|
125
|
+
printResult(io, fail('skill.sync', 'SKILL_SYNC_FAILED', message, { applied: false }, [message]), options.json);
|
|
126
|
+
process.exitCode = 1;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
91
129
|
addJsonOption(skill
|
|
92
130
|
.command('runbook <name>')
|
|
93
131
|
.description('Inspect a skill Default runbook section and its --apply authorization-note status')).action(async (name, options) => {
|
|
@@ -463,10 +501,30 @@ export function registerCoreAndArtifactCommands(program, io) {
|
|
|
463
501
|
});
|
|
464
502
|
addJsonOption(standards
|
|
465
503
|
.command('migrate')
|
|
466
|
-
.description('Rewrite a consumer project CLAUDE.md to drop the legacy heartbeat block (slice 028). Dry-run by default; pass --apply to write.')
|
|
504
|
+
.description('Rewrite a consumer project CLAUDE.md to drop the legacy heartbeat block (slice 028). Dry-run by default; pass --apply to write. With --from-claude-rules, thins the 1.x .claude/rules/ tree to 2-line pointers and scaffolds .peaks/standards/ (slice 2026-06-12-standards-migrate-claude-rules).')
|
|
467
505
|
.option('--project <path>', 'target project root')
|
|
468
|
-
.option('--apply', 'rewrite the legacy block in place; default is dry-run')
|
|
506
|
+
.option('--apply', 'rewrite the legacy block in place; default is dry-run')
|
|
507
|
+
.option('--from-claude-rules', 'thin .claude/rules/ to pointers and scaffold .peaks/standards/ (used by `peaks upgrade --to 2.0`)')).action((options) => {
|
|
469
508
|
const projectRoot = options.project ?? process.cwd();
|
|
509
|
+
if (options.fromClaudeRules === true) {
|
|
510
|
+
try {
|
|
511
|
+
const result = migrateClaudeRules({ projectRoot, apply: options.apply === true });
|
|
512
|
+
printResult(io, ok('standards.migrate', result.data, [], [...result.data.nextActions]), options.json);
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
printResult(io, fail('standards.migrate', 'STANDARDS_MIGRATE_FAILED', getErrorMessage(error), {
|
|
516
|
+
backupPath: null,
|
|
517
|
+
thinnedFiles: [],
|
|
518
|
+
scaffoldedFiles: [],
|
|
519
|
+
preservedFiles: [],
|
|
520
|
+
wouldChange: false,
|
|
521
|
+
applied: false,
|
|
522
|
+
nextActions: [],
|
|
523
|
+
}, [getErrorMessage(error)]), options.json);
|
|
524
|
+
process.exitCode = 1;
|
|
525
|
+
}
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
470
528
|
try {
|
|
471
529
|
const result = migrateStandards({ project: projectRoot, apply: options.apply === true });
|
|
472
530
|
printResult(io, ok('standards.migrate', result.data, [], result.data.nextActions), options.json);
|
|
@@ -519,6 +577,27 @@ export function registerCoreAndArtifactCommands(program, io) {
|
|
|
519
577
|
process.exitCode = 1;
|
|
520
578
|
}
|
|
521
579
|
});
|
|
580
|
+
addJsonOption(memory
|
|
581
|
+
.command('search <query>')
|
|
582
|
+
.description('Fuzzy-search the memory index (deterministic, local, zero-token). Default --limit 6.')
|
|
583
|
+
.option('--kind <kind>', 'filter by memory kind (one of: project, rule, decision, reference, feedback, convention, module, lesson)')
|
|
584
|
+
.option('--limit <n>', 'maximum number of matches to return', (value) => Number(value))
|
|
585
|
+
.option('--project <path>', 'target project root (defaults to git root or cwd)')).action((query, options) => {
|
|
586
|
+
// Lazy import avoids a top-of-file import cycle (memory-commands.ts
|
|
587
|
+
// imports services that the rest of this file may also touch).
|
|
588
|
+
void import('./memory-commands.js').then(({ runMemorySearch }) => {
|
|
589
|
+
runMemorySearch(io, {
|
|
590
|
+
query,
|
|
591
|
+
...(options.kind !== undefined ? { kind: options.kind } : {}),
|
|
592
|
+
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
593
|
+
...(options.project !== undefined ? { project: options.project } : {}),
|
|
594
|
+
...(options.json !== undefined ? { json: options.json } : {}),
|
|
595
|
+
});
|
|
596
|
+
}).catch((error) => {
|
|
597
|
+
printResult(io, fail('memory.search', 'MEMORY_SEARCH_BOOTSTRAP_FAILED', getErrorMessage(error), {}, []), options.json);
|
|
598
|
+
process.exitCode = 1;
|
|
599
|
+
});
|
|
600
|
+
});
|
|
522
601
|
const proxy = program.command('proxy').description('Manage proxy settings');
|
|
523
602
|
addJsonOption(proxy
|
|
524
603
|
.command('test')
|
|
@@ -2,6 +2,10 @@ import { addJsonOption, printResult } from '../cli-helpers.js';
|
|
|
2
2
|
import { detectIdeFromContext, parseAdapterStdin, parseClaudeShapeStdin, pluckObject, pluckString } from '../../services/ide/hook-translator.js';
|
|
3
3
|
import { buildCanonicalHook, formatDecisionResponse } from '../../services/ide/hook-protocol.js';
|
|
4
4
|
import { getAdapter } from '../../services/ide/ide-registry.js';
|
|
5
|
+
import { evaluateSoloCodeBan } from '../../services/audit/enforcers/solo-code-ban.js';
|
|
6
|
+
import { isRootWrite } from '../../services/audit/enforcers/no-root-pollution.js';
|
|
7
|
+
import { checkLoginGate } from '../../services/audit/enforcers/login-gate.js';
|
|
8
|
+
import { resolveActiveSkillForCaller } from '../../services/audit/enforcers/active-skill-resolver.js';
|
|
5
9
|
import { fail, ok } from '../../shared/result.js';
|
|
6
10
|
/**
|
|
7
11
|
* Read the hook payload. `PEAKS_HOOK_STDIN` is a test seam (same convention as
|
|
@@ -77,6 +81,21 @@ export function registerHookHandleCommand(program, io) {
|
|
|
77
81
|
// Dispatch by toolName. For slice #1+, we only handle Bash. Task tool sub-agent dispatch goes through `peaks sub-agent dispatch` (slice #009) and does not need a hook entry.
|
|
78
82
|
// Other tools: allow (no-op; future events will be added here).
|
|
79
83
|
if (hook.toolName === 'Bash' && typeof fallbackCommand === 'string' && fallbackCommand.trim().length > 0) {
|
|
84
|
+
// L2.1 P0 #1: solo-code-ban. Deny `git commit` / `git apply` from peaks-* skills
|
|
85
|
+
// BEFORE the SOP gate runs. The active skill is read from the per-caller
|
|
86
|
+
// active-skill file (see active-skill-resolver.ts).
|
|
87
|
+
const activeSkill = resolveActiveSkillForCaller(projectRoot);
|
|
88
|
+
if (activeSkill.skill !== null) {
|
|
89
|
+
const soloDecision = evaluateSoloCodeBan({ skill: activeSkill.skill, command: fallbackCommand });
|
|
90
|
+
if (soloDecision.denied) {
|
|
91
|
+
const formatted = formatDecisionResponse(ide, 'deny', soloDecision.reason);
|
|
92
|
+
io.stdout(formatted.stdout);
|
|
93
|
+
if (options.json === true) {
|
|
94
|
+
io.stderr(JSON.stringify(ok('hook.handle', { ide, tool: hook.toolName, decision: 'deny', reason: soloDecision.reason, enforcer: 'solo-code-ban' })));
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
80
99
|
// Lazy import to avoid circular: peaks gate enforce logic
|
|
81
100
|
const { enforceBashCommand } = await import('../../services/sop/gate-enforce-service.js');
|
|
82
101
|
const decision = await enforceBashCommand(projectRoot, fallbackCommand);
|
|
@@ -89,6 +108,37 @@ export function registerHookHandleCommand(program, io) {
|
|
|
89
108
|
return;
|
|
90
109
|
}
|
|
91
110
|
}
|
|
111
|
+
// L2.2 P1 #1: login-gate. After solo-code-ban + gate-enforce pass,
|
|
112
|
+
// flag destructive patterns (uninstall, force-push, --force, --hard,
|
|
113
|
+
// rm -rf) so the LLM gets a soft warning (still allow). The user
|
|
114
|
+
// gets the warning via the warn channel; the command proceeds.
|
|
115
|
+
if (hook.toolName === 'Bash' && typeof fallbackCommand === 'string') {
|
|
116
|
+
const gate = checkLoginGate({ command: fallbackCommand });
|
|
117
|
+
if (gate.destructive) {
|
|
118
|
+
io.stderr(`warning: login-gate: destructive command detected (pattern: ${gate.matchedPattern}). Confirm with the user before proceeding.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// L2.1 P0 #2: no-root-pollution. Deny Write/Edit to files outside the
|
|
122
|
+
// root allowlist. file_path is read from toolInput.file_path (Claude
|
|
123
|
+
// and most IDEs use the same shape). When the field is missing or the
|
|
124
|
+
// path is not at depth 1, the enforcer allows (no-op).
|
|
125
|
+
if (hook.toolName === 'Write' || hook.toolName === 'Edit' || hook.toolName === 'MultiEdit' || hook.toolName === 'Create') {
|
|
126
|
+
const filePath = pluckString(parsed, ['tool_input', 'file_path'])
|
|
127
|
+
?? pluckString(parsed, ['toolInput', 'file_path'])
|
|
128
|
+
?? pluckString(parsed, ['tool_input', 'path'])
|
|
129
|
+
?? pluckString(parsed, ['toolInput', 'path']);
|
|
130
|
+
if (typeof filePath === 'string' && filePath.trim().length > 0) {
|
|
131
|
+
const rootCheck = isRootWrite({ projectRoot, filePath });
|
|
132
|
+
if (!rootCheck.allowed) {
|
|
133
|
+
const formatted = formatDecisionResponse(ide, 'deny', rootCheck.denyReason);
|
|
134
|
+
io.stdout(formatted.stdout);
|
|
135
|
+
if (options.json === true) {
|
|
136
|
+
io.stderr(JSON.stringify(ok('hook.handle', { ide, tool: hook.toolName, decision: 'deny', reason: rootCheck.denyReason, enforcer: 'no-root-pollution' })));
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
92
142
|
const allow = formatDecisionResponse(ide, 'allow');
|
|
93
143
|
io.stdout(allow.stdout);
|
|
94
144
|
if (options.json === true) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* peaks loop * CLI (Slice #14) — L4 Agent Loop Integration.
|
|
3
|
+
*
|
|
4
|
+
* Per docs/superpowers/specs/2026-06-11-peaks-cli-l1-l2-l3-redesign.md §5.4
|
|
5
|
+
* Slice #14, ships 5 sub-features:
|
|
6
|
+
*
|
|
7
|
+
* 14.1 peaks loop distill — extract patterns from past sessions
|
|
8
|
+
* (delegates to peaks memory extract)
|
|
9
|
+
* 14.2 peaks loop preflight — pre-run sanity checks (placeholder)
|
|
10
|
+
* 14.3 peaks loop detect-pattern — find repeating patterns (placeholder)
|
|
11
|
+
* 14.4 peaks loop check-consistency — verify state consistency (placeholder)
|
|
12
|
+
* 14.5 peaks goal compose — autonomous goal composition (placeholder;
|
|
13
|
+
* requires IDE adapter goalCommand capability
|
|
14
|
+
* per Slice #0.7)
|
|
15
|
+
*
|
|
16
|
+
* The 4 placeholders emit a clear nextActions list; the LLM-side UX
|
|
17
|
+
* layer composes the actual runtime today.
|
|
18
|
+
*/
|
|
19
|
+
import { Command } from 'commander';
|
|
20
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
21
|
+
export declare function registerLoopCommands(program: Command, io: ProgramIO): void;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* peaks loop * CLI (Slice #14) — L4 Agent Loop Integration.
|
|
3
|
+
*
|
|
4
|
+
* Per docs/superpowers/specs/2026-06-11-peaks-cli-l1-l2-l3-redesign.md §5.4
|
|
5
|
+
* Slice #14, ships 5 sub-features:
|
|
6
|
+
*
|
|
7
|
+
* 14.1 peaks loop distill — extract patterns from past sessions
|
|
8
|
+
* (delegates to peaks memory extract)
|
|
9
|
+
* 14.2 peaks loop preflight — pre-run sanity checks (placeholder)
|
|
10
|
+
* 14.3 peaks loop detect-pattern — find repeating patterns (placeholder)
|
|
11
|
+
* 14.4 peaks loop check-consistency — verify state consistency (placeholder)
|
|
12
|
+
* 14.5 peaks goal compose — autonomous goal composition (placeholder;
|
|
13
|
+
* requires IDE adapter goalCommand capability
|
|
14
|
+
* per Slice #0.7)
|
|
15
|
+
*
|
|
16
|
+
* The 4 placeholders emit a clear nextActions list; the LLM-side UX
|
|
17
|
+
* layer composes the actual runtime today.
|
|
18
|
+
*/
|
|
19
|
+
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
20
|
+
import { fail, ok } from '../../shared/result.js';
|
|
21
|
+
export function registerLoopCommands(program, io) {
|
|
22
|
+
// 14.5 peaks goal compose — registered as a TOP-LEVEL command (not under
|
|
23
|
+
// `peaks loop`) because IDE adapters expose it as `goalCommand`.
|
|
24
|
+
// The sub-agent dispatch path consumes it; the slice 0.7 hermes +
|
|
25
|
+
// openclaw adapters will thread it through.
|
|
26
|
+
addJsonOption(program
|
|
27
|
+
.command('goal')
|
|
28
|
+
.description('14.5: compose an autonomous goal (returns the goal envelope that the LLM-side UX layer feeds to peaks sub-agent dispatch)')
|
|
29
|
+
.requiredOption('--project <path>', 'target project root')
|
|
30
|
+
.requiredOption('--goal <text>', 'the high-level goal to compose')).action(async (options) => {
|
|
31
|
+
try {
|
|
32
|
+
printResult(io, ok('goal.compose', {
|
|
33
|
+
project: options.project,
|
|
34
|
+
goal: options.goal,
|
|
35
|
+
status: 'placeholder',
|
|
36
|
+
nextSteps: [
|
|
37
|
+
'The composed goal is consumed by peaks sub-agent dispatch.',
|
|
38
|
+
'The hermes + openclaw IDE adapters (Slice #0.7) surface this as a goalCommand.',
|
|
39
|
+
],
|
|
40
|
+
}, [], [
|
|
41
|
+
'goal.compose is a thin facade; the LLM-side UX layer decomposes the goal into sub-agent tasks.',
|
|
42
|
+
]), options.json);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
printResult(io, fail('goal.compose', 'GOAL_COMPOSE_FAILED', getErrorMessage(error), { project: options.project, goal: options.goal }, ['Verify the project path and --goal value']), options.json);
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// peaks loop *
|
|
50
|
+
const loop = program.command('loop').description('Slice #14: L4 Agent Loop sub-features (distill / preflight / detect-pattern / check-consistency)');
|
|
51
|
+
// 14.1 distill
|
|
52
|
+
addJsonOption(loop.command('distill')
|
|
53
|
+
.description('14.1: distill patterns from past sessions into .peaks/memory/ (delegates to peaks memory extract)')
|
|
54
|
+
.requiredOption('--project <path>', 'target project root')
|
|
55
|
+
.option('--apply', 'write extracted memories to .peaks/memory/ (default: dry-run preview)', false)).action(async (options) => {
|
|
56
|
+
try {
|
|
57
|
+
const apply = options.apply === true;
|
|
58
|
+
// Delegate to the existing peaks memory extract CLI via dynamic
|
|
59
|
+
// import (avoids circular); the LLM-side UX layer composes the
|
|
60
|
+
// two commands.
|
|
61
|
+
const { execFileSync } = await import('node:child_process');
|
|
62
|
+
const args = ['memory', 'extract', '--project', options.project];
|
|
63
|
+
if (apply)
|
|
64
|
+
args.push('--apply');
|
|
65
|
+
const stdout = execFileSync('node', ['bin/peaks.js', ...args], {
|
|
66
|
+
cwd: options.project,
|
|
67
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
68
|
+
}).toString('utf-8');
|
|
69
|
+
printResult(io, ok('loop.distill', {
|
|
70
|
+
project: options.project,
|
|
71
|
+
apply,
|
|
72
|
+
delegateStdout: stdout.slice(0, 200),
|
|
73
|
+
}, [], [
|
|
74
|
+
apply ? 'peaks memory extract --apply was invoked' : 'peaks memory extract dry-run was invoked',
|
|
75
|
+
'A future slice will inline the memory extract (not via execFileSync).',
|
|
76
|
+
]), options.json);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
printResult(io, fail('loop.distill', 'LOOP_DISTILL_FAILED', getErrorMessage(error), { project: options.project }, ['Verify the project path']), options.json);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// 14.2 preflight
|
|
84
|
+
addJsonOption(loop.command('preflight')
|
|
85
|
+
.description('14.2: pre-run sanity checks (placeholder; future slice runs peaks doctor + peaks audit before each loop iter)')
|
|
86
|
+
.requiredOption('--project <path>', 'target project root')).action(async (options) => {
|
|
87
|
+
printResult(io, ok('loop.preflight', {
|
|
88
|
+
project: options.project,
|
|
89
|
+
status: 'placeholder',
|
|
90
|
+
nextSteps: [
|
|
91
|
+
'For each L4 loop iteration, call peaks doctor + peaks audit to surface regressions.',
|
|
92
|
+
'A future slice will inline the preflight checks (not just placeholder).',
|
|
93
|
+
],
|
|
94
|
+
}, [], [
|
|
95
|
+
'loop.preflight is a thin facade; the LLM-side UX layer composes peaks doctor + peaks audit.',
|
|
96
|
+
]), options.json);
|
|
97
|
+
});
|
|
98
|
+
// 14.3 detect-pattern
|
|
99
|
+
addJsonOption(loop.command('detect-pattern')
|
|
100
|
+
.description('14.3: detect repeating patterns across past sessions (placeholder; future slice uses peaks retrospective search)')
|
|
101
|
+
.requiredOption('--project <path>', 'target project root')).action(async (options) => {
|
|
102
|
+
printResult(io, ok('loop.detect-pattern', {
|
|
103
|
+
project: options.project,
|
|
104
|
+
status: 'placeholder',
|
|
105
|
+
nextSteps: [
|
|
106
|
+
'Run peaks retrospective search --limit 50 to surface high-frequency patterns.',
|
|
107
|
+
'A future slice will rank by frequency + LLM confidence.',
|
|
108
|
+
],
|
|
109
|
+
}, [], [
|
|
110
|
+
'loop.detect-pattern is a thin facade; the LLM-side UX layer composes peaks retrospective search.',
|
|
111
|
+
]), options.json);
|
|
112
|
+
});
|
|
113
|
+
// 14.4 check-consistency
|
|
114
|
+
addJsonOption(loop.command('check-consistency')
|
|
115
|
+
.description('14.4: verify state consistency (placeholder; future slice compares .peaks/_runtime across sessions)')
|
|
116
|
+
.requiredOption('--project <path>', 'target project root')).action(async (options) => {
|
|
117
|
+
printResult(io, ok('loop.check-consistency', {
|
|
118
|
+
project: options.project,
|
|
119
|
+
status: 'placeholder',
|
|
120
|
+
nextSteps: [
|
|
121
|
+
'Compare .peaks/_runtime/<sid>/session.json across recent sessions for drift.',
|
|
122
|
+
'A future slice will report drift with severity (warn / fail).',
|
|
123
|
+
],
|
|
124
|
+
}, [], [
|
|
125
|
+
'loop.check-consistency is a thin facade; the LLM-side UX layer composes the drift scan.',
|
|
126
|
+
]), options.json);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
2
|
+
export interface MemorySearchCommandOptions {
|
|
3
|
+
query: string;
|
|
4
|
+
kind?: string;
|
|
5
|
+
limit?: number;
|
|
6
|
+
project?: string;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Run the memory search subcommand. Extracted so unit tests can
|
|
11
|
+
* exercise the full envelope without spawning a subprocess.
|
|
12
|
+
*/
|
|
13
|
+
export declare function runMemorySearch(io: ProgramIO, options: MemorySearchCommandOptions): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
2
|
+
import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
|
|
3
|
+
import { searchMemory } from '../../services/memory/memory-search-service.js';
|
|
4
|
+
import { fail, ok } from '../../shared/result.js';
|
|
5
|
+
import { getErrorMessage, printResult } from '../cli-helpers.js';
|
|
6
|
+
const VALID_KINDS = [
|
|
7
|
+
'project',
|
|
8
|
+
'rule',
|
|
9
|
+
'decision',
|
|
10
|
+
'reference',
|
|
11
|
+
'feedback',
|
|
12
|
+
'convention',
|
|
13
|
+
'module',
|
|
14
|
+
'lesson',
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Run the memory search subcommand. Extracted so unit tests can
|
|
18
|
+
* exercise the full envelope without spawning a subprocess.
|
|
19
|
+
*/
|
|
20
|
+
export function runMemorySearch(io, options) {
|
|
21
|
+
const projectRoot = options.project !== undefined
|
|
22
|
+
? resolveCanonicalProjectRoot(options.project)
|
|
23
|
+
: (findProjectRoot(process.cwd()) ?? process.cwd());
|
|
24
|
+
const kindFilter = options.kind !== undefined && VALID_KINDS.includes(options.kind)
|
|
25
|
+
? options.kind
|
|
26
|
+
: undefined;
|
|
27
|
+
// When the user passes --kind but the value isn't in the valid set,
|
|
28
|
+
// we silently pass `undefined` so the search returns the full set;
|
|
29
|
+
// that's friendlier than a hard error and matches the spec's
|
|
30
|
+
// "invalid kind -> empty matches" semantic for the filter path.
|
|
31
|
+
// (For the loader unit test we exercise the explicit-invalid path
|
|
32
|
+
// directly; here the CLI side is forgiving.)
|
|
33
|
+
try {
|
|
34
|
+
const matches = searchMemory({
|
|
35
|
+
query: options.query,
|
|
36
|
+
projectRoot,
|
|
37
|
+
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
38
|
+
...(kindFilter !== undefined ? { kind: kindFilter } : {}),
|
|
39
|
+
});
|
|
40
|
+
printResult(io, ok('memory.search', {
|
|
41
|
+
query: options.query,
|
|
42
|
+
total: matches.length,
|
|
43
|
+
matches,
|
|
44
|
+
warnings: [],
|
|
45
|
+
}, []), options.json);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const message = getErrorMessage(error);
|
|
49
|
+
const code = error.code ?? 'MEMORY_SEARCH_FAILED';
|
|
50
|
+
const suggestions = [];
|
|
51
|
+
if (code === 'INDEX_MISSING') {
|
|
52
|
+
suggestions.push('Run `peaks memory extract --apply` to build the index from memory/*.md files');
|
|
53
|
+
}
|
|
54
|
+
if (code === 'EMPTY_QUERY') {
|
|
55
|
+
suggestions.push('Use `peaks memory index` to list all entries');
|
|
56
|
+
}
|
|
57
|
+
printResult(io, fail('memory.search', code, message, { projectRoot, query: options.query }, suggestions), options.json);
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -5,6 +5,8 @@ import { renderOpenSpecChange } from '../../services/openspec/openspec-render-se
|
|
|
5
5
|
import { validateOpenSpecChange } from '../../services/openspec/openspec-validate-service.js';
|
|
6
6
|
import { archiveOpenSpecChange } from '../../services/openspec/openspec-archive-service.js';
|
|
7
7
|
import { executeOpenSpecInit } from '../../services/openspec/openspec-init-service.js';
|
|
8
|
+
import { proposeFromDoctor } from '../../services/openspec/openspec-propose-from-doctor-service.js';
|
|
9
|
+
import { runDoctor } from '../../services/doctor/doctor-service.js';
|
|
8
10
|
import { fail, ok } from '../../shared/result.js';
|
|
9
11
|
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
10
12
|
function resolveScanOptions(project) {
|
|
@@ -197,4 +199,39 @@ export function registerOpenSpecCommands(program, io) {
|
|
|
197
199
|
process.exitCode = 1;
|
|
198
200
|
}
|
|
199
201
|
});
|
|
202
|
+
addJsonOption(openspec
|
|
203
|
+
.command('from-doctor')
|
|
204
|
+
.description('Slice L3.3: generate an OpenSpec change draft (proposal.md) from a peaks doctor finding')
|
|
205
|
+
.requiredOption('--project <path>', 'target project root')
|
|
206
|
+
.requiredOption('--check-id <id>', 'the doctor check id to draft from (e.g. L3:l3-memory-health)')).action(async (options) => {
|
|
207
|
+
try {
|
|
208
|
+
const report = await runDoctor();
|
|
209
|
+
const finding = report.checks.find((c) => c.id === options.checkId);
|
|
210
|
+
if (finding === undefined) {
|
|
211
|
+
printResult(io, fail('openspec.from-doctor', 'CHECK_NOT_FOUND', `No doctor check with id "${options.checkId}"`, { availableIds: report.checks.map((c) => c.id) }, ['Run peaks doctor --json to list available check ids']), options.json);
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (finding.ok) {
|
|
216
|
+
printResult(io, fail('openspec.from-doctor', 'CHECK_ALREADY_PASSING', `Doctor check "${options.checkId}" is already passing; nothing to draft`, { checkId: options.checkId }, ['Pick a failing check from peaks doctor --json']), options.json);
|
|
217
|
+
process.exitCode = 1;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const doctorFinding = {
|
|
221
|
+
id: finding.id,
|
|
222
|
+
rule: finding.rule ?? finding.id,
|
|
223
|
+
detail: finding.message,
|
|
224
|
+
severity: 'fail',
|
|
225
|
+
};
|
|
226
|
+
const result = proposeFromDoctor({ projectRoot: options.project, finding: doctorFinding });
|
|
227
|
+
printResult(io, ok('openspec.from-doctor', result, [], [
|
|
228
|
+
`draft proposal written to ${result.proposalPath}`,
|
|
229
|
+
'Review + edit the draft, then run `peaks openspec validate <id>`',
|
|
230
|
+
]), options.json);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
printResult(io, fail('openspec.from-doctor', 'OPENSPEC_FROM_DOCTOR_FAILED', getErrorMessage(error), { projectRoot: options.project, checkId: options.checkId }, ['Verify the project path and the check id']), options.json);
|
|
234
|
+
process.exitCode = 1;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
200
237
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { loadPreferences, preferencesPath, savePreferences, } from '../../services/preferences/preferences-service.js';
|
|
4
|
+
const ALLOWED_KEYS = new Set([
|
|
5
|
+
'economyMode',
|
|
6
|
+
'swarmMode',
|
|
7
|
+
'uaPrompt',
|
|
8
|
+
'agentShieldPrompt',
|
|
9
|
+
'classifyConservatism',
|
|
10
|
+
'classifyRules',
|
|
11
|
+
'headroom',
|
|
12
|
+
'swarmSpeculative',
|
|
13
|
+
'loopAutonomousEnabled',
|
|
14
|
+
]);
|
|
15
|
+
export function registerPreferencesCommands(program) {
|
|
16
|
+
const prefs = program
|
|
17
|
+
.command('preferences')
|
|
18
|
+
.description('Manage project-local preferences (`.peaks/preferences.json`)');
|
|
19
|
+
prefs
|
|
20
|
+
.command('get')
|
|
21
|
+
.description('Get a preference value (from override or default)')
|
|
22
|
+
.requiredOption('--key <key>', 'preference key')
|
|
23
|
+
.option('--project <path>', 'project root', process.cwd())
|
|
24
|
+
.option('--json', 'JSON envelope output')
|
|
25
|
+
.action((opts) => {
|
|
26
|
+
try {
|
|
27
|
+
if (!ALLOWED_KEYS.has(opts.key)) {
|
|
28
|
+
throw new Error(`PREFERENCES_KEY_UNKNOWN: ${opts.key}`);
|
|
29
|
+
}
|
|
30
|
+
const all = loadPreferences(opts.project);
|
|
31
|
+
const value = all[opts.key];
|
|
32
|
+
const filePath = preferencesPath(opts.project);
|
|
33
|
+
// Spec bug fix: use the top-level existsSync import (the spec's
|
|
34
|
+
// `await import('node:fs')` would not compile inside a non-async action).
|
|
35
|
+
// Source is 'override' only when the key is explicitly present in
|
|
36
|
+
// the on-disk file (not merely when the file exists, since reset()
|
|
37
|
+
// may leave the file with other keys still overridden).
|
|
38
|
+
let source = 'default';
|
|
39
|
+
if (existsSync(filePath)) {
|
|
40
|
+
try {
|
|
41
|
+
const raw = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
42
|
+
if (Object.prototype.hasOwnProperty.call(raw, opts.key)) {
|
|
43
|
+
source = 'override';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Corrupt file: report the merged value but fall back to 'default'
|
|
48
|
+
// source to avoid claiming an override that cannot be parsed.
|
|
49
|
+
source = 'default';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Stable ordering: source is computed, value reflects merged state.
|
|
53
|
+
const envelope = {
|
|
54
|
+
ok: true,
|
|
55
|
+
data: { key: opts.key, value, source },
|
|
56
|
+
};
|
|
57
|
+
process.stdout.write(JSON.stringify(envelope, null, 2) + '\n');
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
process.stderr.write(err.message + '\n');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
prefs
|
|
65
|
+
.command('set')
|
|
66
|
+
.description('Override a preference value (writes to .peaks/preferences.json)')
|
|
67
|
+
.requiredOption('--key <key>', 'preference key')
|
|
68
|
+
.requiredOption('--value <value>', 'value (parsed as JSON, or string if not JSON)')
|
|
69
|
+
.option('--project <path>', 'project root', process.cwd())
|
|
70
|
+
.option('--json', 'JSON envelope output')
|
|
71
|
+
.action((opts) => {
|
|
72
|
+
try {
|
|
73
|
+
if (!ALLOWED_KEYS.has(opts.key)) {
|
|
74
|
+
throw new Error(`PREFERENCES_KEY_UNKNOWN: ${opts.key}`);
|
|
75
|
+
}
|
|
76
|
+
let parsed = opts.value;
|
|
77
|
+
try {
|
|
78
|
+
parsed = JSON.parse(opts.value);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Not valid JSON — keep the raw string.
|
|
82
|
+
}
|
|
83
|
+
const merged = savePreferences(opts.project, {
|
|
84
|
+
[opts.key]: parsed,
|
|
85
|
+
});
|
|
86
|
+
const envelope = {
|
|
87
|
+
ok: true,
|
|
88
|
+
data: {
|
|
89
|
+
key: opts.key,
|
|
90
|
+
value: merged[opts.key],
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
process.stdout.write(JSON.stringify(envelope, null, 2) + '\n');
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
process.stderr.write(err.message + '\n');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
prefs
|
|
101
|
+
.command('reset')
|
|
102
|
+
.description('Remove the override for a key (falls back to default)')
|
|
103
|
+
.requiredOption('--key <key>', 'preference key')
|
|
104
|
+
.option('--project <path>', 'project root', process.cwd())
|
|
105
|
+
.option('--json', 'JSON envelope output')
|
|
106
|
+
.action((opts) => {
|
|
107
|
+
try {
|
|
108
|
+
if (!ALLOWED_KEYS.has(opts.key)) {
|
|
109
|
+
throw new Error(`PREFERENCES_KEY_UNKNOWN: ${opts.key}`);
|
|
110
|
+
}
|
|
111
|
+
const filePath = preferencesPath(opts.project);
|
|
112
|
+
// Read raw on-disk state (not the merged view) so removing a key
|
|
113
|
+
// truly removes the override, instead of savePreferences re-inserting
|
|
114
|
+
// the default value. If no file exists, there is nothing to reset.
|
|
115
|
+
if (!existsSync(filePath)) {
|
|
116
|
+
const envelope = {
|
|
117
|
+
ok: true,
|
|
118
|
+
data: { key: opts.key, removed: false, reason: 'no-override-file' },
|
|
119
|
+
};
|
|
120
|
+
process.stdout.write(JSON.stringify(envelope) + '\n');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
let raw;
|
|
124
|
+
try {
|
|
125
|
+
raw = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
throw new Error(`PREFERENCES_JSON_INVALID: failed to parse ${filePath}: ${err.message}`);
|
|
129
|
+
}
|
|
130
|
+
const hadKey = Object.prototype.hasOwnProperty.call(raw, opts.key);
|
|
131
|
+
if (hadKey) {
|
|
132
|
+
delete raw[opts.key];
|
|
133
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
134
|
+
writeFileSync(filePath, JSON.stringify(raw, null, 2) + '\n', 'utf8');
|
|
135
|
+
}
|
|
136
|
+
const envelope = {
|
|
137
|
+
ok: true,
|
|
138
|
+
data: { key: opts.key, removed: hadKey },
|
|
139
|
+
};
|
|
140
|
+
process.stdout.write(JSON.stringify(envelope) + '\n');
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
process.stderr.write(err.message + '\n');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { type ProgramIO } from '../cli-helpers.js';
|
|
3
|
+
export interface RetrospectiveSearchCommandOptions {
|
|
4
|
+
query: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
outcome?: string;
|
|
7
|
+
limit?: number;
|
|
8
|
+
project?: string;
|
|
9
|
+
json?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function runRetrospectiveSearch(io: ProgramIO, options: RetrospectiveSearchCommandOptions): void;
|
|
3
12
|
export declare function registerRetrospectiveCommands(program: Command, io: ProgramIO): void;
|