peaks-loop 4.1.2 → 4.1.3
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 +136 -47
- package/CHANGELOG.md +56 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/contracts/readonly-argv-whitelist.json +211 -0
- package/dist/cli/commands/audit-artifact-runners.d.ts +16 -0
- package/dist/cli/commands/audit-artifact-runners.js +170 -0
- package/dist/cli/commands/audit-command-shared.d.ts +97 -0
- package/dist/cli/commands/audit-command-shared.js +60 -0
- package/dist/cli/commands/audit-commands.d.ts +7 -31
- package/dist/cli/commands/audit-commands.js +64 -473
- package/dist/cli/commands/audit-goal-runners.d.ts +17 -0
- package/dist/cli/commands/audit-goal-runners.js +114 -0
- package/dist/cli/commands/audit-red-lines-runners.d.ts +11 -0
- package/dist/cli/commands/audit-red-lines-runners.js +43 -0
- package/dist/cli/commands/audit-refusal-data.d.ts +23 -0
- package/dist/cli/commands/audit-refusal-data.js +69 -0
- package/dist/cli/commands/audit-static-runners.d.ts +13 -0
- package/dist/cli/commands/audit-static-runners.js +139 -0
- package/dist/cli/commands/await-runners.d.ts +11 -0
- package/dist/cli/commands/await-runners.js +148 -0
- package/dist/cli/commands/code-auto-compact-command.d.ts +3 -0
- package/dist/cli/commands/code-auto-compact-command.js +124 -0
- package/dist/cli/commands/code-auto-compact-envelope.d.ts +22 -0
- package/dist/cli/commands/code-auto-compact-envelope.js +39 -0
- package/dist/cli/commands/code-context-audit-command.d.ts +3 -0
- package/dist/cli/commands/code-context-audit-command.js +68 -0
- package/dist/cli/commands/code-context-now-command.d.ts +3 -0
- package/dist/cli/commands/code-context-now-command.js +124 -0
- package/dist/cli/commands/code-context-now-envelope.d.ts +66 -0
- package/dist/cli/commands/code-context-now-envelope.js +136 -0
- package/dist/cli/commands/code-context-now-mapping.d.ts +27 -0
- package/dist/cli/commands/code-context-now-mapping.js +43 -0
- package/dist/cli/commands/code-emit-handoff-command.d.ts +3 -0
- package/dist/cli/commands/code-emit-handoff-command.js +99 -0
- package/dist/cli/commands/code-gate-step-08-command.d.ts +3 -0
- package/dist/cli/commands/code-gate-step-08-command.js +139 -0
- package/dist/cli/commands/code-post-compact-detect-command.d.ts +3 -0
- package/dist/cli/commands/code-post-compact-detect-command.js +50 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +9 -43
- package/dist/cli/commands/code-runtime-commands.js +20 -672
- package/dist/cli/commands/code-runtime-session.d.ts +1 -0
- package/dist/cli/commands/code-runtime-session.js +15 -0
- package/dist/cli/commands/compact-command.d.ts +6 -2
- package/dist/cli/commands/compact-command.js +12 -559
- package/dist/cli/commands/compact-dry-run-command.d.ts +3 -0
- package/dist/cli/commands/compact-dry-run-command.js +87 -0
- package/dist/cli/commands/compact-force-command.d.ts +3 -0
- package/dist/cli/commands/compact-force-command.js +98 -0
- package/dist/cli/commands/compact-harness-window-command.d.ts +3 -0
- package/dist/cli/commands/compact-harness-window-command.js +174 -0
- package/dist/cli/commands/compact-history-command.d.ts +3 -0
- package/dist/cli/commands/compact-history-command.js +89 -0
- package/dist/cli/commands/compact-read-commands.d.ts +3 -0
- package/dist/cli/commands/compact-read-commands.js +123 -0
- package/dist/cli/commands/compact-settle-command.d.ts +3 -0
- package/dist/cli/commands/compact-settle-command.js +130 -0
- package/dist/cli/commands/compact-shared.d.ts +9 -0
- package/dist/cli/commands/compact-shared.js +26 -0
- package/dist/cli/commands/core/skill-command.d.ts +1 -44
- package/dist/cli/commands/core/skill-command.js +13 -541
- package/dist/cli/commands/core/skill-context-verdict.d.ts +38 -0
- package/dist/cli/commands/core/skill-context-verdict.js +63 -0
- package/dist/cli/commands/core/skill-generated-config-notice.d.ts +41 -0
- package/dist/cli/commands/core/skill-generated-config-notice.js +75 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.js +50 -0
- package/dist/cli/commands/core/skill-presence-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-commands.js +123 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.js +165 -0
- package/dist/cli/commands/core/skill-registry-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-registry-commands.js +117 -0
- package/dist/cli/commands/core/skill-runbook-command.d.ts +3 -0
- package/dist/cli/commands/core/skill-runbook-command.js +29 -0
- package/dist/cli/commands/dispatch-command-spec.d.ts +2 -0
- package/dist/cli/commands/dispatch-command-spec.js +62 -0
- package/dist/cli/commands/dispatch-commands.js +159 -775
- package/dist/cli/commands/dispatch-detached-branch.d.ts +3 -0
- package/dist/cli/commands/dispatch-detached-branch.js +58 -0
- package/dist/cli/commands/dispatch-envelope.d.ts +36 -0
- package/dist/cli/commands/dispatch-envelope.js +104 -0
- package/dist/cli/commands/dispatch-isolation-lease.d.ts +17 -0
- package/dist/cli/commands/dispatch-isolation-lease.js +149 -0
- package/dist/cli/commands/dispatch-preflight.d.ts +7 -0
- package/dist/cli/commands/dispatch-preflight.js +105 -0
- package/dist/cli/commands/dispatch-prompt-blocks.d.ts +10 -0
- package/dist/cli/commands/dispatch-prompt-blocks.js +56 -0
- package/dist/cli/commands/dispatch-prompt-composition.d.ts +30 -0
- package/dist/cli/commands/dispatch-prompt-composition.js +171 -0
- package/dist/cli/commands/dispatch-record-write.d.ts +24 -0
- package/dist/cli/commands/dispatch-record-write.js +96 -0
- package/dist/cli/commands/dispatch-tool-call.d.ts +15 -0
- package/dist/cli/commands/dispatch-tool-call.js +128 -0
- package/dist/cli/commands/evolution-command-shared.d.ts +78 -0
- package/dist/cli/commands/evolution-command-shared.js +107 -0
- package/dist/cli/commands/evolution-commands.d.ts +11 -1
- package/dist/cli/commands/evolution-commands.js +19 -350
- package/dist/cli/commands/evolution-evaluate-command.d.ts +24 -0
- package/dist/cli/commands/evolution-evaluate-command.js +138 -0
- package/dist/cli/commands/evolution-propose-command.d.ts +23 -0
- package/dist/cli/commands/evolution-propose-command.js +145 -0
- package/dist/cli/commands/evolution-status-command.d.ts +17 -0
- package/dist/cli/commands/evolution-status-command.js +41 -0
- package/dist/cli/commands/evolution-verdict-commands.d.ts +30 -0
- package/dist/cli/commands/evolution-verdict-commands.js +100 -0
- package/dist/cli/commands/finalize-accumulator.d.ts +44 -0
- package/dist/cli/commands/finalize-accumulator.js +55 -0
- package/dist/cli/commands/finalize-branches.d.ts +34 -0
- package/dist/cli/commands/finalize-branches.js +119 -0
- package/dist/cli/commands/finalize-runners.d.ts +3 -0
- package/dist/cli/commands/finalize-runners.js +92 -0
- package/dist/cli/commands/finalize-selection.d.ts +65 -0
- package/dist/cli/commands/finalize-selection.js +35 -0
- package/dist/cli/commands/gate-commands.js +26 -8
- package/dist/cli/commands/job-checkpoint-commands.d.ts +3 -0
- package/dist/cli/commands/job-checkpoint-commands.js +157 -0
- package/dist/cli/commands/job-command-shared.d.ts +109 -0
- package/dist/cli/commands/job-command-shared.js +72 -0
- package/dist/cli/commands/job-commands.d.ts +4 -64
- package/dist/cli/commands/job-commands.js +42 -552
- package/dist/cli/commands/job-cost-check-command.d.ts +3 -0
- package/dist/cli/commands/job-cost-check-command.js +41 -0
- package/dist/cli/commands/job-init-status-commands.d.ts +3 -0
- package/dist/cli/commands/job-init-status-commands.js +149 -0
- package/dist/cli/commands/job-run-commands.d.ts +3 -0
- package/dist/cli/commands/job-run-commands.js +97 -0
- package/dist/cli/commands/job-state-root.d.ts +35 -0
- package/dist/cli/commands/job-state-root.js +76 -0
- package/dist/cli/commands/loop-eval-capture-index.d.ts +4 -0
- package/dist/cli/commands/loop-eval-capture-index.js +44 -0
- package/dist/cli/commands/loop-eval-commands.d.ts +8 -5
- package/dist/cli/commands/loop-eval-commands.js +12 -626
- package/dist/cli/commands/loop-eval-eval-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-eval-command.js +168 -0
- package/dist/cli/commands/loop-eval-monotonic-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-monotonic-command.js +90 -0
- package/dist/cli/commands/loop-eval-parents.d.ts +7 -0
- package/dist/cli/commands/loop-eval-parents.js +20 -0
- package/dist/cli/commands/loop-eval-run-code.d.ts +7 -0
- package/dist/cli/commands/loop-eval-run-code.js +40 -0
- package/dist/cli/commands/loop-eval-run-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-run-command.js +113 -0
- package/dist/cli/commands/loop-eval-spec-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-commands.js +165 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.js +42 -0
- package/dist/cli/commands/loop-eval-workflow-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-workflow-commands.js +141 -0
- package/dist/cli/commands/mcp-commands.d.ts +10 -0
- package/dist/cli/commands/mcp-commands.js +157 -0
- package/dist/cli/commands/request-command-options.d.ts +49 -0
- package/dist/cli/commands/request-command-options.js +1 -0
- package/dist/cli/commands/request-commands.d.ts +3 -9
- package/dist/cli/commands/request-commands.js +17 -522
- package/dist/cli/commands/request-init-command.d.ts +3 -0
- package/dist/cli/commands/request-init-command.js +100 -0
- package/dist/cli/commands/request-lint-command.d.ts +3 -0
- package/dist/cli/commands/request-lint-command.js +50 -0
- package/dist/cli/commands/request-list-command.d.ts +10 -0
- package/dist/cli/commands/request-list-command.js +53 -0
- package/dist/cli/commands/request-repair-status-command.d.ts +3 -0
- package/dist/cli/commands/request-repair-status-command.js +61 -0
- package/dist/cli/commands/request-show-command.d.ts +3 -0
- package/dist/cli/commands/request-show-command.js +56 -0
- package/dist/cli/commands/request-transition-command.d.ts +3 -0
- package/dist/cli/commands/request-transition-command.js +122 -0
- package/dist/cli/commands/request-transition-failures.d.ts +8 -0
- package/dist/cli/commands/request-transition-failures.js +90 -0
- package/dist/cli/commands/request-transition-hooks.d.ts +50 -0
- package/dist/cli/commands/request-transition-hooks.js +115 -0
- package/dist/cli/commands/request-transition-preflight.d.ts +10 -0
- package/dist/cli/commands/request-transition-preflight.js +107 -0
- package/dist/cli/commands/scan-command-shared.d.ts +96 -0
- package/dist/cli/commands/scan-command-shared.js +83 -0
- package/dist/cli/commands/scan-commands.d.ts +3 -2
- package/dist/cli/commands/scan-commands.js +23 -463
- package/dist/cli/commands/scan-inventory-commands.d.ts +3 -0
- package/dist/cli/commands/scan-inventory-commands.js +96 -0
- package/dist/cli/commands/scan-project-commands.d.ts +3 -0
- package/dist/cli/commands/scan-project-commands.js +65 -0
- package/dist/cli/commands/scan-scope-commands.d.ts +3 -0
- package/dist/cli/commands/scan-scope-commands.js +149 -0
- package/dist/cli/commands/scan-scope-helpers.d.ts +8 -0
- package/dist/cli/commands/scan-scope-helpers.js +54 -0
- package/dist/cli/commands/scan-structural-commands.d.ts +3 -0
- package/dist/cli/commands/scan-structural-commands.js +113 -0
- package/dist/cli/commands/share-commands.d.ts +12 -68
- package/dist/cli/commands/share-commands.js +47 -556
- package/dist/cli/commands/share-error-hints.d.ts +14 -0
- package/dist/cli/commands/share-error-hints.js +38 -0
- package/dist/cli/commands/share-identity.d.ts +13 -0
- package/dist/cli/commands/share-identity.js +22 -0
- package/dist/cli/commands/share-record-paths.d.ts +37 -0
- package/dist/cli/commands/share-record-paths.js +68 -0
- package/dist/cli/commands/share-runners.d.ts +3 -0
- package/dist/cli/commands/share-runners.js +158 -0
- package/dist/cli/commands/shared-read-runners.d.ts +3 -0
- package/dist/cli/commands/shared-read-runners.js +51 -0
- package/dist/cli/commands/slice-check-command.d.ts +3 -0
- package/dist/cli/commands/slice-check-command.js +54 -0
- package/dist/cli/commands/slice-commands.d.ts +2 -22
- package/dist/cli/commands/slice-commands.js +18 -517
- package/dist/cli/commands/slice-decompose-artifacts.d.ts +18 -0
- package/dist/cli/commands/slice-decompose-artifacts.js +79 -0
- package/dist/cli/commands/slice-decompose-command.d.ts +3 -0
- package/dist/cli/commands/slice-decompose-command.js +123 -0
- package/dist/cli/commands/slice-list-commands.d.ts +3 -0
- package/dist/cli/commands/slice-list-commands.js +182 -0
- package/dist/cli/commands/slice-pick-command.d.ts +3 -0
- package/dist/cli/commands/slice-pick-command.js +60 -0
- package/dist/cli/commands/slice-plan-command.d.ts +24 -0
- package/dist/cli/commands/slice-plan-command.js +111 -0
- package/dist/cli/commands/workflow-commands.d.ts +10 -1
- package/dist/cli/commands/workflow-commands.js +6 -594
- package/dist/cli/commands/workflow-gate-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-gate-commands.js +116 -0
- package/dist/cli/commands/workflow-plan-helpers.d.ts +48 -0
- package/dist/cli/commands/workflow-plan-helpers.js +103 -0
- package/dist/cli/commands/workflow-planning-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-planning-commands.js +56 -0
- package/dist/cli/commands/workflow-swarm-actions.d.ts +7 -0
- package/dist/cli/commands/workflow-swarm-actions.js +109 -0
- package/dist/cli/commands/workflow-swarm-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-swarm-commands.js +138 -0
- package/dist/cli/commands/workflow-tech-actions.d.ts +10 -0
- package/dist/cli/commands/workflow-tech-actions.js +150 -0
- package/dist/cli/commands/workspace/init-actions.d.ts +10 -0
- package/dist/cli/commands/workspace/init-actions.js +199 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.d.ts +10 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.js +54 -0
- package/dist/cli/commands/workspace/init-command.d.ts +8 -124
- package/dist/cli/commands/workspace/init-command.js +59 -623
- package/dist/cli/commands/workspace/init-context.d.ts +58 -0
- package/dist/cli/commands/workspace/init-context.js +121 -0
- package/dist/cli/commands/workspace/init-envelope.d.ts +3 -0
- package/dist/cli/commands/workspace/init-envelope.js +34 -0
- package/dist/cli/commands/workspace/init-failures.d.ts +3 -0
- package/dist/cli/commands/workspace/init-failures.js +84 -0
- package/dist/cli/commands/workspace/init-hooks-decision.d.ts +24 -0
- package/dist/cli/commands/workspace/init-hooks-decision.js +189 -0
- package/dist/cli/commands/workspace/init-options.d.ts +102 -0
- package/dist/cli/commands/workspace/init-options.js +1 -0
- package/dist/cli/commands/worktree-lease-commands.d.ts +10 -6
- package/dist/cli/commands/worktree-lease-commands.js +42 -512
- package/dist/cli/commands/worktree-lease-gc-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-gc-command.js +117 -0
- package/dist/cli/commands/worktree-lease-git.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-git.js +48 -0
- package/dist/cli/commands/worktree-lease-list-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-list-command.js +100 -0
- package/dist/cli/commands/worktree-lease-load.d.ts +37 -0
- package/dist/cli/commands/worktree-lease-load.js +28 -0
- package/dist/cli/commands/worktree-lease-release-command.d.ts +13 -0
- package/dist/cli/commands/worktree-lease-release-command.js +105 -0
- package/dist/cli/commands/worktree-lease-renew-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-renew-command.js +99 -0
- package/dist/cli/commands/worktree-lease-session.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-session.js +29 -0
- package/dist/cli/commands/worktree-lease-spawn-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-spawn-command.js +119 -0
- package/dist/cli/commands/worktree-lease-status-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-status-command.js +86 -0
- package/dist/cli/commands/worktree-lease-store.d.ts +19 -0
- package/dist/cli/commands/worktree-lease-store.js +24 -0
- package/dist/cli/program.js +5 -0
- package/dist/services/distribution/mcp-install-runner.d.ts +21 -0
- package/dist/services/distribution/mcp-install-runner.js +59 -0
- package/dist/services/distribution/mcp-install.d.ts +95 -0
- package/dist/services/distribution/mcp-install.js +179 -0
- package/dist/services/distribution/plugin-manifest.d.ts +97 -0
- package/dist/services/distribution/plugin-manifest.js +126 -0
- package/dist/services/hooks/mcp-surface-gate.d.ts +19 -0
- package/dist/services/hooks/mcp-surface-gate.js +87 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +4 -35
- package/dist/services/ide/adapters/claude-code-adapter.js +48 -171
- package/dist/services/ide/adapters/claude-code-local-state.d.ts +28 -0
- package/dist/services/ide/adapters/claude-code-local-state.js +112 -0
- package/dist/services/ide/adapters/claude-code-model-window.d.ts +37 -0
- package/dist/services/ide/adapters/claude-code-model-window.js +113 -0
- package/dist/services/ide/ide-mcp-install-types.d.ts +78 -0
- package/dist/services/ide/ide-mcp-install-types.js +104 -0
- package/dist/services/ide/ide-types.d.ts +45 -1
- package/dist/services/ide/mcp-tool-matcher.d.ts +34 -0
- package/dist/services/ide/mcp-tool-matcher.js +84 -0
- package/dist/services/lint/npx-resolver.js +7 -0
- package/dist/services/mcp/cli-executor.d.ts +36 -0
- package/dist/services/mcp/cli-executor.js +97 -0
- package/dist/services/mcp/json-rpc.d.ts +81 -0
- package/dist/services/mcp/json-rpc.js +116 -0
- package/dist/services/mcp/memory-tool.d.ts +3 -0
- package/dist/services/mcp/memory-tool.js +62 -0
- package/dist/services/mcp/server-main.d.ts +1 -0
- package/dist/services/mcp/server-main.js +66 -0
- package/dist/services/mcp/server.d.ts +22 -0
- package/dist/services/mcp/server.js +152 -0
- package/dist/services/mcp/surface.d.ts +62 -0
- package/dist/services/mcp/surface.js +198 -0
- package/dist/services/mcp/tool-core.d.ts +42 -0
- package/dist/services/mcp/tool-core.js +137 -0
- package/dist/services/mcp/tools.d.ts +13 -0
- package/dist/services/mcp/tools.js +58 -0
- package/dist/services/readonly-surface/argv-guard.d.ts +32 -0
- package/dist/services/readonly-surface/argv-guard.js +164 -0
- package/dist/services/readonly-surface/readonly-whitelist.d.ts +147 -0
- package/dist/services/readonly-surface/readonly-whitelist.js +107 -0
- package/dist/services/readonly-surface/whitelist-generator.d.ts +64 -0
- package/dist/services/readonly-surface/whitelist-generator.js +212 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +26 -0
- package/dist/services/skills/hooks-settings-service.js +10 -2
- package/package.json +17 -6
- package/scripts/install-skills-bundles.mjs +233 -0
- package/scripts/install-skills-fs.mjs +148 -0
- package/scripts/install-skills-ide.mjs +279 -0
- package/scripts/install-skills-output-style-default.mjs +196 -0
- package/scripts/install-skills-output-styles.mjs +121 -0
- package/scripts/install-skills-report.mjs +42 -0
- package/scripts/install-skills-user-config.mjs +198 -0
- package/scripts/install-skills.mjs +36 -1066
- package/scripts/watch-tree.mjs +140 -0
- package/scripts/watch.mjs +4 -126
- package/skills/peaks-status/SKILL.md +31 -13
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalize a user-supplied `--project <path>` value.
|
|
3
|
+
*
|
|
4
|
+
* Git Bash on Windows hands us forward-slash paths
|
|
5
|
+
* (`C:/Users/.../peaks-loop`) while `peaks workspace init` writes the
|
|
6
|
+
* backslash form, and either side may carry a trailing separator or
|
|
7
|
+
* differing case. Resolving to the real path here means every
|
|
8
|
+
* downstream consumer (`getSessionId`, `setSessionMeta`,
|
|
9
|
+
* `setSkillPresence`) sees one stable form.
|
|
10
|
+
*
|
|
11
|
+
* Returns the input unchanged when it cannot be resolved (path does
|
|
12
|
+
* not exist yet, or is not readable) so a bad `--project` still
|
|
13
|
+
* reaches the existing error handling rather than throwing here.
|
|
14
|
+
*/
|
|
15
|
+
export declare function canonicalizeProjectOption(project: string | undefined): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* D1 (2026-09-15) — generated-config staleness, carried on `skill presence`.
|
|
18
|
+
*
|
|
19
|
+
* WHY THIS CALL. `npm i -g peaks-loop@<newer>` upgrades the CLI and leaves the
|
|
20
|
+
* project's generated config exactly as the OLD release wrote it:
|
|
21
|
+
* `initWorkspace` is the only writer and `ensureSession` early-returns once a
|
|
22
|
+
* session is bound, so nothing re-runs the generator. `.claude/settings.local.json`
|
|
23
|
+
* is drift-checked — but only on an init that never comes. The user found the
|
|
24
|
+
* previous instance of this by deleting their `.claude/*.json` and restarting;
|
|
25
|
+
* nothing in the product told them to.
|
|
26
|
+
*
|
|
27
|
+
* `peaks skill presence` is the ONE peaks call every skill makes in every turn
|
|
28
|
+
* (CLAUDE.md mandates it at the start of every response), so it is the only
|
|
29
|
+
* channel guaranteed to carry a drift notice to the LLM that can act on it —
|
|
30
|
+
* the same reasoning as the loop-hygiene verdict attached one screen down. A
|
|
31
|
+
* rule that lives only in a SKILL.md body is compacted away; a warning that
|
|
32
|
+
* rides the per-turn tool output is not.
|
|
33
|
+
*
|
|
34
|
+
* The field is additive and present only when stale, so no existing consumer
|
|
35
|
+
* of the envelope changes shape. `null` projectRoot means the caller had no
|
|
36
|
+
* project to inspect — skipped, not assumed stale.
|
|
37
|
+
*/
|
|
38
|
+
export declare function generatedConfigNotice(projectRoot: string | undefined): {
|
|
39
|
+
field: Record<string, unknown>;
|
|
40
|
+
warnings: string[];
|
|
41
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Split out of `core/skill-command.ts`:
|
|
2
|
+
// `--project` canonicalization plus the D1 generated-config staleness notice
|
|
3
|
+
// that rides the per-turn `peaks skill presence` read.
|
|
4
|
+
import { stableRealPath } from '../../../shared/path-utils.js';
|
|
5
|
+
import { detectStaleGeneratedArtifacts } from '../../../services/workspace/generated-artifacts-stamp.js';
|
|
6
|
+
/**
|
|
7
|
+
* Canonicalize a user-supplied `--project <path>` value.
|
|
8
|
+
*
|
|
9
|
+
* Git Bash on Windows hands us forward-slash paths
|
|
10
|
+
* (`C:/Users/.../peaks-loop`) while `peaks workspace init` writes the
|
|
11
|
+
* backslash form, and either side may carry a trailing separator or
|
|
12
|
+
* differing case. Resolving to the real path here means every
|
|
13
|
+
* downstream consumer (`getSessionId`, `setSessionMeta`,
|
|
14
|
+
* `setSkillPresence`) sees one stable form.
|
|
15
|
+
*
|
|
16
|
+
* Returns the input unchanged when it cannot be resolved (path does
|
|
17
|
+
* not exist yet, or is not readable) so a bad `--project` still
|
|
18
|
+
* reaches the existing error handling rather than throwing here.
|
|
19
|
+
*/
|
|
20
|
+
export function canonicalizeProjectOption(project) {
|
|
21
|
+
if (project === undefined)
|
|
22
|
+
return undefined;
|
|
23
|
+
try {
|
|
24
|
+
return stableRealPath(project);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return project;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* D1 (2026-09-15) — generated-config staleness, carried on `skill presence`.
|
|
32
|
+
*
|
|
33
|
+
* WHY THIS CALL. `npm i -g peaks-loop@<newer>` upgrades the CLI and leaves the
|
|
34
|
+
* project's generated config exactly as the OLD release wrote it:
|
|
35
|
+
* `initWorkspace` is the only writer and `ensureSession` early-returns once a
|
|
36
|
+
* session is bound, so nothing re-runs the generator. `.claude/settings.local.json`
|
|
37
|
+
* is drift-checked — but only on an init that never comes. The user found the
|
|
38
|
+
* previous instance of this by deleting their `.claude/*.json` and restarting;
|
|
39
|
+
* nothing in the product told them to.
|
|
40
|
+
*
|
|
41
|
+
* `peaks skill presence` is the ONE peaks call every skill makes in every turn
|
|
42
|
+
* (CLAUDE.md mandates it at the start of every response), so it is the only
|
|
43
|
+
* channel guaranteed to carry a drift notice to the LLM that can act on it —
|
|
44
|
+
* the same reasoning as the loop-hygiene verdict attached one screen down. A
|
|
45
|
+
* rule that lives only in a SKILL.md body is compacted away; a warning that
|
|
46
|
+
* rides the per-turn tool output is not.
|
|
47
|
+
*
|
|
48
|
+
* The field is additive and present only when stale, so no existing consumer
|
|
49
|
+
* of the envelope changes shape. `null` projectRoot means the caller had no
|
|
50
|
+
* project to inspect — skipped, not assumed stale.
|
|
51
|
+
*/
|
|
52
|
+
export function generatedConfigNotice(projectRoot) {
|
|
53
|
+
if (projectRoot === undefined)
|
|
54
|
+
return { field: {}, warnings: [] };
|
|
55
|
+
const staleness = detectStaleGeneratedArtifacts(projectRoot);
|
|
56
|
+
if (!staleness.stale)
|
|
57
|
+
return { field: {}, warnings: [] };
|
|
58
|
+
const onDiskVersion = staleness.onDisk?.packageVersion ?? '(unstamped)';
|
|
59
|
+
return {
|
|
60
|
+
field: {
|
|
61
|
+
generatedArtifacts: {
|
|
62
|
+
stale: true,
|
|
63
|
+
reasons: staleness.reasons,
|
|
64
|
+
onDiskPackageVersion: staleness.onDisk?.packageVersion ?? null,
|
|
65
|
+
installedPackageVersion: staleness.expected.packageVersion
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
warnings: [
|
|
69
|
+
`Generated config at '${projectRoot}' was produced by peaks-loop ${onDiskVersion} ` +
|
|
70
|
+
`but ${staleness.expected.packageVersion} is installed (${staleness.reasons.join(', ')}). ` +
|
|
71
|
+
`Re-run \`peaks workspace init\` ` +
|
|
72
|
+
`to regenerate .claude/settings.local.json and the offline template copy.`
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getSkillPresence, touchSkillHeartbeat } from '../../../services/skills/skill-presence-service.js';
|
|
2
|
+
import { detectPresenceMarker } from '../../../services/hooks/presence-marker-detector.js';
|
|
3
|
+
import { findProjectRoot } from '../../../services/config/config-safety.js';
|
|
4
|
+
import { ok } from 'peaks-loop-shared/result';
|
|
5
|
+
import { addJsonOption, printResult } from '../../cli-helpers.js';
|
|
6
|
+
const HEARTBEAT_DESCRIPTION = 'Show the heartbeat status of the active Peaks skill';
|
|
7
|
+
const HEARTBEAT_TOUCH_DESCRIPTION = 'Update the heartbeat timestamp (called by the LLM each turn to confirm peaks skill context is alive)';
|
|
8
|
+
const DETECT_MARKER_LOSS_DESCRIPTION = 'Detect whether the latest assistant message lost the Peaks-Loop status header while a peaks skill is still active (slice 028 detection primitive).';
|
|
9
|
+
const DETECT_MARKER_LOSS_MESSAGE_HELP = 'latest assistant message text to scan (defaults to reading the most recent LLM response from the stdin pipe, or empty string when no pipe is attached)';
|
|
10
|
+
function runSkillHeartbeat(options, io) {
|
|
11
|
+
const presence = getSkillPresence();
|
|
12
|
+
if (presence === null) {
|
|
13
|
+
printResult(io, ok('skill.heartbeat', { active: false, heartbeat: 'none' }), options.json);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
printResult(io, ok('skill.heartbeat', {
|
|
17
|
+
active: true,
|
|
18
|
+
skill: presence.skill,
|
|
19
|
+
gate: presence.gate ?? null,
|
|
20
|
+
lastHeartbeat: presence.lastHeartbeat ?? presence.setAt,
|
|
21
|
+
setAt: presence.setAt
|
|
22
|
+
}), options.json);
|
|
23
|
+
}
|
|
24
|
+
function runSkillHeartbeatTouch(options, io) {
|
|
25
|
+
const updated = touchSkillHeartbeat();
|
|
26
|
+
if (updated === null) {
|
|
27
|
+
printResult(io, ok('skill.heartbeat:touch', { active: false, heartbeat: 'none' }), options.json);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
printResult(io, ok('skill.heartbeat:touch', {
|
|
31
|
+
active: true,
|
|
32
|
+
skill: updated.skill,
|
|
33
|
+
lastHeartbeat: updated.lastHeartbeat
|
|
34
|
+
}), options.json);
|
|
35
|
+
}
|
|
36
|
+
function runSkillDetectMarkerLoss(options, io) {
|
|
37
|
+
const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
38
|
+
const message = options.message ?? '';
|
|
39
|
+
const result = detectPresenceMarker({ project: projectRoot, latestAssistantMessage: message });
|
|
40
|
+
printResult(io, ok('skill.detect-marker-loss', result), options.json);
|
|
41
|
+
}
|
|
42
|
+
export function registerSkillHeartbeatCommands(skill, io) {
|
|
43
|
+
addJsonOption(skill.command('heartbeat').description(HEARTBEAT_DESCRIPTION)).action((options) => runSkillHeartbeat(options, io));
|
|
44
|
+
addJsonOption(skill.command('heartbeat:touch').description(HEARTBEAT_TOUCH_DESCRIPTION)).action((options) => runSkillHeartbeatTouch(options, io));
|
|
45
|
+
addJsonOption(skill
|
|
46
|
+
.command('detect-marker-loss')
|
|
47
|
+
.description(DETECT_MARKER_LOSS_DESCRIPTION)
|
|
48
|
+
.option('--project <path>', 'project root path (auto-detected from cwd when omitted)')
|
|
49
|
+
.option('--message <text>', DETECT_MARKER_LOSS_MESSAGE_HELP)).action((options) => runSkillDetectMarkerLoss(options, io));
|
|
50
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { setSkillPresence, getSkillPresence, isSkillPresenceMode, checkStalePresence } from '../../../services/skills/skill-presence-service.js';
|
|
2
|
+
import { findProjectRoot } from '../../../services/config/config-safety.js';
|
|
3
|
+
import { getSessionId, setSessionMeta } from '../../../services/session/session-manager.js';
|
|
4
|
+
import { resolveCallerProjection } from '../../../services/session/resolve-caller-id.js';
|
|
5
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
6
|
+
import { addJsonOption, printResult } from '../../cli-helpers.js';
|
|
7
|
+
import { canonicalizeProjectOption, generatedConfigNotice } from './skill-generated-config-notice.js';
|
|
8
|
+
import { contextVerdict } from './skill-context-verdict.js';
|
|
9
|
+
const PRESENCE_DESCRIPTION = 'Show the currently active Peaks skill (alias: presence:get)';
|
|
10
|
+
const PRESENCE_CHECK_STALE_HELP = 'slice 002 (v2.15.0): also report whether the recorded outer session id still matches the current one. Default false (back-compat).';
|
|
11
|
+
const PRESENCE_PROJECT_HELP = 'project root (default: cwd)';
|
|
12
|
+
const PRESENCE_SET_DESCRIPTION = 'Set the currently active Peaks skill for session-wide visibility. Slice 4.0.8: requires a bound session and an adapter-resolved caller id (fail-closed); raw unlink is rejected.';
|
|
13
|
+
const PRESENCE_SET_MODE_HELP = 'execution mode';
|
|
14
|
+
const PRESENCE_SET_GATE_HELP = 'current gate';
|
|
15
|
+
const PRESENCE_SET_PROJECT_HELP = 'project root path (auto-detected from cwd when omitted)';
|
|
16
|
+
function presenceActiveData(presence, parts) {
|
|
17
|
+
return {
|
|
18
|
+
active: true,
|
|
19
|
+
...presence,
|
|
20
|
+
...(parts.verdict.context !== null ? { context: parts.verdict.context } : {}),
|
|
21
|
+
...parts.generatedConfig.field
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function presenceStaleData(presence, staleness, parts) {
|
|
25
|
+
return {
|
|
26
|
+
active: true,
|
|
27
|
+
...presence,
|
|
28
|
+
stale: staleness.stale,
|
|
29
|
+
staleReason: staleness.reason,
|
|
30
|
+
currentOuterSessionId: staleness.currentOuterSessionId,
|
|
31
|
+
recordedOuterSessionId: staleness.recordedOuterSessionId,
|
|
32
|
+
...(parts.verdict.context !== null ? { context: parts.verdict.context } : {}),
|
|
33
|
+
...parts.generatedConfig.field
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function runSkillPresence(options, io) {
|
|
37
|
+
const projectOption = canonicalizeProjectOption(options.project);
|
|
38
|
+
const generatedConfig = generatedConfigNotice(projectOption ?? findProjectRoot(process.cwd()) ?? process.cwd());
|
|
39
|
+
const presence = getSkillPresence(projectOption);
|
|
40
|
+
if (presence === null) {
|
|
41
|
+
printResult(io, ok('skill.presence', { active: false, ...generatedConfig.field }, generatedConfig.warnings), options.json);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Loop-hygiene verdict: attached to every ACTIVE read, so the
|
|
45
|
+
// zero-pause contract travels with the one call every skill already
|
|
46
|
+
// makes — in every mode and every consumer project.
|
|
47
|
+
const verdict = contextVerdict(projectOption ?? process.cwd());
|
|
48
|
+
const parts = { verdict, generatedConfig };
|
|
49
|
+
// `--check-stale` lets callers (peaks-code Step 1, statusline) get both
|
|
50
|
+
// pieces of info from a single CLI invocation. The presence is returned
|
|
51
|
+
// UNCHANGED — it is a read-only flag, not a clear.
|
|
52
|
+
const data = options.checkStale === true
|
|
53
|
+
? presenceStaleData(presence, checkStalePresence({ projectRootOverride: projectOption }), parts)
|
|
54
|
+
: presenceActiveData(presence, parts);
|
|
55
|
+
printResult(io, ok('skill.presence', data, generatedConfig.warnings, verdict.nextActions), options.json);
|
|
56
|
+
}
|
|
57
|
+
function printInvalidMode(io, options, name) {
|
|
58
|
+
printResult(io, fail('skill.presence:set', 'INVALID_MODE', `Invalid mode: ${options.mode} (expected one of: full-auto, assisted, strict, 24h)`, { name, mode: options.mode }, ['Use a valid mode: full-auto, assisted, strict, or 24h']), options.json);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
}
|
|
61
|
+
function printSessionNotBound(io, options, name, projectRoot) {
|
|
62
|
+
printResult(io, fail('skill.presence:set', 'PEAKS_SESSION_NOT_BOUND', 'No canonical peaks session is bound for this project (RD §3 D1).', { projectRoot, name }, ['Run `peaks workspace init --project <p>` first, then re-run `peaks skill presence:set`.']), options.json);
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
}
|
|
65
|
+
function printCallerNotResolved(io, options, target, err) {
|
|
66
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
67
|
+
printResult(io, fail('skill.presence:set', 'PEAKS_CALLER_NOT_RESOLVED', `Active IDE adapter could not resolve a callerId (RD §3 D1): ${message}`, target, [
|
|
68
|
+
'Ensure the active IDE is detected by `peaks` and the IDE session variable is set.',
|
|
69
|
+
'Or set PEAKS_CALLER_ID=<id> in the environment for scripted usage.'
|
|
70
|
+
]), options.json);
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
}
|
|
73
|
+
function runSkillPresenceSet(name, options, io) {
|
|
74
|
+
const projectOption = canonicalizeProjectOption(options.project);
|
|
75
|
+
const projectRoot = projectOption ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
76
|
+
if (options.mode !== undefined && !isSkillPresenceMode(options.mode)) {
|
|
77
|
+
printInvalidMode(io, options, name);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Slice 4.0.8 (D1 + D2): `presence:set` is fail-closed. We refuse
|
|
81
|
+
// any write when (a) no peaks session is bound, or (b) the active
|
|
82
|
+
// IDE adapter cannot resolve a valid callerId. Both failures
|
|
83
|
+
// surface BEFORE any filesystem write. The legacy
|
|
84
|
+
// `setSkillPresence` wrapper is kept as a compat shim for tests
|
|
85
|
+
// and CLI flows that intentionally do not need a session — but
|
|
86
|
+
// production CLI traffic must go through this gate.
|
|
87
|
+
const boundSessionId = getSessionId(projectRoot);
|
|
88
|
+
if (boundSessionId === null) {
|
|
89
|
+
printSessionNotBound(io, options, name, projectRoot);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
resolveCallerProjection({ projectRoot });
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
printCallerNotResolved(io, options, { name, projectRoot }, err);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const presence = setSkillPresence(name, options.mode, options.gate, projectOption);
|
|
100
|
+
// Session metadata is updated when a session is bound (read-only
|
|
101
|
+
// path: `getSessionId`). We do not auto-spawn a session.
|
|
102
|
+
if (boundSessionId !== null) {
|
|
103
|
+
setSessionMeta(projectRoot, boundSessionId, {
|
|
104
|
+
skill: name,
|
|
105
|
+
...(options.mode ? { mode: options.mode } : {}),
|
|
106
|
+
...(options.gate ? { gate: options.gate } : {})
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
printResult(io, ok('skill.presence:set', { active: true, ...presence }), options.json);
|
|
110
|
+
}
|
|
111
|
+
export function registerSkillPresenceCommands(skill, io) {
|
|
112
|
+
addJsonOption(skill
|
|
113
|
+
.command('presence')
|
|
114
|
+
.description(PRESENCE_DESCRIPTION)
|
|
115
|
+
.option('--check-stale', PRESENCE_CHECK_STALE_HELP)
|
|
116
|
+
.option('--project <path>', PRESENCE_PROJECT_HELP)).action((options) => runSkillPresence(options, io));
|
|
117
|
+
addJsonOption(skill
|
|
118
|
+
.command('presence:set <name>')
|
|
119
|
+
.description(PRESENCE_SET_DESCRIPTION)
|
|
120
|
+
.option('--mode <mode>', PRESENCE_SET_MODE_HELP)
|
|
121
|
+
.option('--gate <gate>', PRESENCE_SET_GATE_HELP)
|
|
122
|
+
.option('--project <path>', PRESENCE_SET_PROJECT_HELP)).action((name, options) => runSkillPresenceSet(name, options, io));
|
|
123
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { clearSkillPresence, getSkillPresence, checkStalePresence } from '../../../services/skills/skill-presence-service.js';
|
|
2
|
+
import { findProjectRoot } from '../../../services/config/config-safety.js';
|
|
3
|
+
import { generateProjectContext } from '../../../services/memory/project-context-service.js';
|
|
4
|
+
import { gcStalePresenceLeases } from '../../../services/skills/presence-lease-service.js';
|
|
5
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
6
|
+
import { addJsonOption, getErrorMessage, printResult } from '../../cli-helpers.js';
|
|
7
|
+
const PRESENCE_CLEAR_DESCRIPTION = 'Unlink the DEPRECATED pre-4.0.11 single-slot presence marker files (`.peaks/_runtime/active-skill.json`, `.peaks/.active-skill.json`). It does NOT terminalize a live presence lease — a workflow-bound lease terminalizes through `peaks workflow terminalize --workflow <id> --reason <reason>`, an ad-hoc lease only at session exit, and raw unlink is FORBIDDEN for both. The envelope reports what is still active.';
|
|
8
|
+
const PRESENCE_CLEAR_PROJECT_HELP = 'project root path (auto-detected from cwd when omitted)';
|
|
9
|
+
const LEASE_GC_DESCRIPTION = 'Manually sweep stale presence leases for the canonical project. Both predicates required: now - lastHeartbeat > 1h AND now - startedAt > 24h. Drained leases are classified; corrupt graphs surface as PEAKS_GRAPH_REF_BROKEN warnings and are excluded.';
|
|
10
|
+
const LEASE_GC_PROJECT_HELP = 'project root (default: cwd)';
|
|
11
|
+
const LEASE_GC_NOW_HELP = 'override the current time (test seam)';
|
|
12
|
+
const PRESENCE_CHECK_STALE_DESCRIPTION = 'Slice 002 (v2.15.0) AC-1: report whether the recorded presence outer session id still matches the current outer session id. ' +
|
|
13
|
+
'Returns { stale: boolean, reason: "outer-session-mismatch" | "no-presence" | null }. ' +
|
|
14
|
+
'Pure read-only — does NOT clear the presence (use `peaks skill presence:clear` for that).';
|
|
15
|
+
const PRESENCE_CHECK_STALE_PROJECT_HELP = 'project root (default: cwd)';
|
|
16
|
+
const PRESENCE_CHECK_STALE_CURRENT_OUTER_HELP = 'override the current outer session id (test seam; default: read from PEAKS_OUTER_SESSION_ID / CLAUDE_CODE_SESSION_ID)';
|
|
17
|
+
const PRESENCE_CLEAR_SURVIVOR_ACTIONS = [
|
|
18
|
+
'A workflow-bound lease terminalizes through `peaks workflow terminalize --workflow <id> --reason <reason>`.',
|
|
19
|
+
'An ad-hoc lease (`peaks skill presence:set`) is terminalizable only at session exit; `presence:clear` will not remove it, and raw unlink is FORBIDDEN.',
|
|
20
|
+
'Run `peaks skill presence --json` to read the lease that survived.'
|
|
21
|
+
];
|
|
22
|
+
const LEASE_GC_NEXT_ACTIONS = [
|
|
23
|
+
'GC predicate: now - lastHeartbeat > 1h AND now - startedAt > 24h (RD §3 D2 + D3).',
|
|
24
|
+
'Re-run `peaks workspace init` or `peaks skill presence:set` to sweep the same project on the bound trigger.'
|
|
25
|
+
];
|
|
26
|
+
function presenceClearData(active, removed, cleared) {
|
|
27
|
+
return {
|
|
28
|
+
active,
|
|
29
|
+
removed,
|
|
30
|
+
cleared,
|
|
31
|
+
...(cleared ? {} : { reason: 'live-lease-survives-presence-clear' }),
|
|
32
|
+
projectContextUpdated: true
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function runPresenceClear(options, io) {
|
|
36
|
+
const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
37
|
+
// What this command actually is: a stale-marker cleanup for projects
|
|
38
|
+
// carrying a pre-4.0.11 single-slot file, and nothing more. The previous
|
|
39
|
+
// description claimed it "routes workflow leases through `workflow
|
|
40
|
+
// terminalize`" and that a "compat wrapper re-routes [it] to
|
|
41
|
+
// `terminalizePresenceLease` when a workflow binding is present" — there is
|
|
42
|
+
// no such wrapper, and no such routing happens here or in
|
|
43
|
+
// `clearSkillPresence`. A caller who read that line and ran this command
|
|
44
|
+
// expecting a live workflow lease to terminalize got a successful exit code
|
|
45
|
+
// and a still-running lease.
|
|
46
|
+
const removed = clearSkillPresence(options.project);
|
|
47
|
+
// Auto-update project context so future sessions have up-to-date history.
|
|
48
|
+
// bootstraps `.peaks/project-scan/` (idempotent). Await the async
|
|
49
|
+
// signature; failure is still non-fatal so we don't block the clear.
|
|
50
|
+
try {
|
|
51
|
+
await generateProjectContext(projectRoot);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
55
|
+
// non-fatal: context update failure should not block presence clear
|
|
56
|
+
}
|
|
57
|
+
// Report the state the project is actually in, not the state this command
|
|
58
|
+
// intended to reach. `clearSkillPresence` unlinks only the DEPRECATED
|
|
59
|
+
// single-slot marker files; the canonical sid-scoped lease is deliberately
|
|
60
|
+
// left alive for an AD-HOC lease (raw unlink is FORBIDDEN — only session
|
|
61
|
+
// exit may terminalize one; workflow leases route through `workflow
|
|
62
|
+
// terminalize`). So after a `presence:set` the very next `peaks skill
|
|
63
|
+
// presence` call still reads `active: true`. A hardcoded `active: false`
|
|
64
|
+
// therefore reported a state the command never reached, and the caller had
|
|
65
|
+
// no way to tell that from a real clear.
|
|
66
|
+
//
|
|
67
|
+
// Re-reading through `getSkillPresence` — the same projection the
|
|
68
|
+
// `skill presence` command serves — is the only derivation that cannot
|
|
69
|
+
// drift from what the next command prints: it is the same read path, on
|
|
70
|
+
// the same project root, evaluated after every write this command makes.
|
|
71
|
+
const active = getSkillPresence(projectRoot) !== null;
|
|
72
|
+
// `cleared` is the live question the caller is actually asking ("is it gone
|
|
73
|
+
// now?"), which `removed` could never answer: a legacy marker being unlinked
|
|
74
|
+
// says nothing about the lease the next command will read. When it is false
|
|
75
|
+
// the envelope says WHY, in the two routes that can terminalize a lease —
|
|
76
|
+
// without this the caller saw `active: true, removed: false` and had no way
|
|
77
|
+
// to tell "you ran the wrong command" from "this command has no opinion".
|
|
78
|
+
const cleared = !active;
|
|
79
|
+
printResult(io, ok('skill.presence:clear', presenceClearData(active, removed, cleared), [], cleared ? [] : PRESENCE_CLEAR_SURVIVOR_ACTIONS), options.json);
|
|
80
|
+
}
|
|
81
|
+
function leaseGcData(result) {
|
|
82
|
+
return {
|
|
83
|
+
envelopeVersion: '4.0.8',
|
|
84
|
+
removed: result.removed,
|
|
85
|
+
retained: result.retained,
|
|
86
|
+
trigger: result.trigger,
|
|
87
|
+
inFlightBatch: result.inFlightBatch,
|
|
88
|
+
warnings: result.warnings,
|
|
89
|
+
errors: result.errors
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Slice 4.0.8 (RD §4): manual lease GC primitive. LLM-coordinated;
|
|
93
|
+
// never a user-typed requirement. The user / LLM runner invokes
|
|
94
|
+
// `peaks skill lease gc --project <p>` to drain leases that meet
|
|
95
|
+
// both stale predicates (24h start AND 1h heartbeat) for the
|
|
96
|
+
// canonical project. Returns a typed envelope so the runner can
|
|
97
|
+
// branch on the aggregate counters.
|
|
98
|
+
// Note: `.command('lease gc')` implicitly creates the `lease`
|
|
99
|
+
// parent under `skill`; we MUST NOT also call `.command('lease')`
|
|
100
|
+
// separately, or Commander.js throws "cannot add command 'lease' as
|
|
101
|
+
// already have command 'lease'" at startup.
|
|
102
|
+
async function runLeaseGc(options, io) {
|
|
103
|
+
const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
104
|
+
try {
|
|
105
|
+
const result = await gcStalePresenceLeases({
|
|
106
|
+
projectRoot,
|
|
107
|
+
...(options.now !== undefined ? { now: options.now } : {}),
|
|
108
|
+
trigger: 'manual'
|
|
109
|
+
});
|
|
110
|
+
printResult(io, ok('skill.lease.gc', leaseGcData(result), result.warnings.map((w) => `${w.code}: ${w.message}`), LEASE_GC_NEXT_ACTIONS), options.json);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
printResult(io, fail('skill.lease.gc', 'PEAKS_LEASE_GC_FAILED', getErrorMessage(err), { projectRoot }, [
|
|
114
|
+
'Re-run with a valid --project and ensure the session is bound.'
|
|
115
|
+
]), options.json);
|
|
116
|
+
process.exitCode = 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// peaks-code Step 1 (and `peaks code should-pause --step
|
|
120
|
+
// step-1-mode-select`) calls this to decide whether the recorded
|
|
121
|
+
// `mode` field can be trusted or whether the LLM must AskUserQuestion.
|
|
122
|
+
function runPresenceCheckStale(options, io) {
|
|
123
|
+
// when the user omits the flag. The service-layer branch
|
|
124
|
+
// `'currentOuter' in opts` returns true for an explicit
|
|
125
|
+
// `undefined` (the key exists on the spread object literal),
|
|
126
|
+
// which would skip the env-var fallback and pin `current =
|
|
127
|
+
// undefined` — always reading the presence as stale. Build a
|
|
128
|
+
// sparse opts object so the service can fall back to
|
|
129
|
+
// `getCurrentOuterSessionId()` (reads PEAKS_OUTER_SESSION_ID /
|
|
130
|
+
// CLAUDE_CODE_SESSION_ID).
|
|
131
|
+
const checkOpts = options.project !== undefined ? { projectRootOverride: options.project } : {};
|
|
132
|
+
if (options.currentOuter !== undefined) {
|
|
133
|
+
checkOpts.currentOuter = options.currentOuter;
|
|
134
|
+
}
|
|
135
|
+
const result = checkStalePresence(checkOpts);
|
|
136
|
+
// Always emit `currentOuterSessionId` in the JSON envelope (even
|
|
137
|
+
// tooling (statusline, sub-agent dispatch) reads the field by
|
|
138
|
+
// name, never by `data.currentOuterSessionId ?? ''`. JSON.stringify
|
|
139
|
+
// drops `undefined` properties, so we coerce to '' before
|
|
140
|
+
// wrapping in the envelope.
|
|
141
|
+
const data = {
|
|
142
|
+
stale: result.stale,
|
|
143
|
+
reason: result.reason,
|
|
144
|
+
presence: result.presence,
|
|
145
|
+
currentOuterSessionId: result.currentOuterSessionId ?? '',
|
|
146
|
+
recordedOuterSessionId: result.recordedOuterSessionId ?? ''
|
|
147
|
+
};
|
|
148
|
+
printResult(io, ok('skill.presence:check-stale', data), options.json);
|
|
149
|
+
}
|
|
150
|
+
export function registerSkillPresenceHousekeepingCommands(skill, io) {
|
|
151
|
+
addJsonOption(skill
|
|
152
|
+
.command('presence:clear')
|
|
153
|
+
.description(PRESENCE_CLEAR_DESCRIPTION)
|
|
154
|
+
.option('--project <path>', PRESENCE_CLEAR_PROJECT_HELP)).action((options) => runPresenceClear(options, io));
|
|
155
|
+
addJsonOption(skill
|
|
156
|
+
.command('lease gc')
|
|
157
|
+
.description(LEASE_GC_DESCRIPTION)
|
|
158
|
+
.option('--project <path>', LEASE_GC_PROJECT_HELP)
|
|
159
|
+
.option('--now <iso>', LEASE_GC_NOW_HELP)).action((options) => runLeaseGc(options, io));
|
|
160
|
+
addJsonOption(skill
|
|
161
|
+
.command('presence:check-stale')
|
|
162
|
+
.description(PRESENCE_CHECK_STALE_DESCRIPTION)
|
|
163
|
+
.option('--project <path>', PRESENCE_CHECK_STALE_PROJECT_HELP)
|
|
164
|
+
.option('--current-outer <id>', PRESENCE_CHECK_STALE_CURRENT_OUTER_HELP)).action((options) => runPresenceCheckStale(options, io));
|
|
165
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { runDoctor } from '../../../services/doctor/index.js';
|
|
2
|
+
import { listSkills } from '../../../services/skills/skill-registry.js';
|
|
3
|
+
import { runSkillSync, SYNC_PLATFORMS } from '../../../services/skills/sync-service.js';
|
|
4
|
+
import { ok, fail } from 'peaks-loop-shared/result';
|
|
5
|
+
import { addJsonOption, getErrorMessage, printResult } from '../../cli-helpers.js';
|
|
6
|
+
const PLATFORM_IDS = SYNC_PLATFORMS.join(', ');
|
|
7
|
+
const LIST_DESCRIPTION = 'List skills derived from skills/*/SKILL.md';
|
|
8
|
+
const LIST_INCLUDE_INTERNAL_HELP = 'include skills with visibility: internal (default: hide them)';
|
|
9
|
+
const DOCTOR_DESCRIPTION = 'Run skill-related doctor checks';
|
|
10
|
+
const SYNC_DESCRIPTION = `Sync the peaks-* skill family to one or all of the 8 supported LLM-CLI platforms (${PLATFORM_IDS}). Idempotent.`;
|
|
11
|
+
const SYNC_PLATFORM_HELP = `sync only one platform (default: --all). Valid: ${PLATFORM_IDS}`;
|
|
12
|
+
const SYNC_ALL_HELP = 'sync all 8 platforms (default if --platform is omitted)';
|
|
13
|
+
const SYNC_DRY_RUN_HELP = 'do not write; emit the same shape with applied=false';
|
|
14
|
+
const SYNC_RECONCILE_HELP = 'repair Peaks-managed skill Junctions whose targets were deleted with a host worktree';
|
|
15
|
+
const SYNC_PROJECT_HELP = 'project root (default: cwd)';
|
|
16
|
+
function sortSkillsForDisplay(skills) {
|
|
17
|
+
return [...skills].sort((a, b) => {
|
|
18
|
+
// door) — list it FIRST so users discover the dispatcher before
|
|
19
|
+
// any specific leaf. Followed by peaks-sop (current default
|
|
20
|
+
// runbook showcase) and peaks-code (canonical code-domain
|
|
21
|
+
// orchestrator); everything else falls back to alphabetical.
|
|
22
|
+
if (a.name === 'peaks-solo')
|
|
23
|
+
return -1;
|
|
24
|
+
if (b.name === 'peaks-solo')
|
|
25
|
+
return 1;
|
|
26
|
+
if (a.name === 'peaks-sop')
|
|
27
|
+
return -1;
|
|
28
|
+
if (b.name === 'peaks-sop')
|
|
29
|
+
return 1;
|
|
30
|
+
if (a.name === 'peaks-code')
|
|
31
|
+
return -1;
|
|
32
|
+
if (b.name === 'peaks-code')
|
|
33
|
+
return 1;
|
|
34
|
+
return a.name.localeCompare(b.name);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function runSkillList(options, io) {
|
|
38
|
+
return listSkills().then((all) => {
|
|
39
|
+
const skills = options.includeInternal === true ? all : all.filter((s) => s.visibility !== 'internal');
|
|
40
|
+
if (options.json === true) {
|
|
41
|
+
printResult(io, ok('skill.list', { skills }), true);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const skill of sortSkillsForDisplay(skills)) {
|
|
45
|
+
io.stdout(` ${skill.name.padEnd(14)}${skill.description}`);
|
|
46
|
+
}
|
|
47
|
+
io.stdout(`\n Invoke any skill by typing its name in conversation (e.g. \`peaks-sop\`).`);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function runSkillDoctor(options, io) {
|
|
51
|
+
return runDoctor().then((report) => {
|
|
52
|
+
const skillChecks = report.checks.filter((check) => check.id.startsWith('skill'));
|
|
53
|
+
const failed = skillChecks.filter((check) => !check.ok).length;
|
|
54
|
+
if (options.json === true) {
|
|
55
|
+
printResult(io, ok('skill.doctor', { checks: skillChecks, ok: failed === 0 }), true);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
for (const check of skillChecks) {
|
|
59
|
+
const icon = check.ok ? '+' : '×';
|
|
60
|
+
io.stdout(` ${icon} ${check.message}`);
|
|
61
|
+
}
|
|
62
|
+
io.stdout(`\n ${skillChecks.length - failed} passed, ${failed} failed`);
|
|
63
|
+
if (failed > 0) {
|
|
64
|
+
io.stderr('\nOne or more skill checks failed.');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (failed > 0) {
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function runSkillSyncCommand(options, io) {
|
|
73
|
+
try {
|
|
74
|
+
const projectRoot = options.project ?? process.cwd();
|
|
75
|
+
const platforms = options.platform !== undefined ? [options.platform] : undefined;
|
|
76
|
+
const result = await runSkillSync({
|
|
77
|
+
projectRoot,
|
|
78
|
+
...(platforms !== undefined ? { platforms } : {}),
|
|
79
|
+
...(options.dryRun === true ? { dryRun: true } : {}),
|
|
80
|
+
...(options.reconcileJunctions === true ? { reconcileJunctions: true } : {})
|
|
81
|
+
});
|
|
82
|
+
const envelope = ok('skill.sync', result, [], [
|
|
83
|
+
`syncedCount: ${result.syncedCount}/${result.perPlatform.length} platforms`,
|
|
84
|
+
`totalInstalled: ${result.totalInstalled} skill symlinks`,
|
|
85
|
+
result.failedCount > 0
|
|
86
|
+
? `failedCount: ${result.failedCount} (run \`peaks skill status\` for details)`
|
|
87
|
+
: 'no failures'
|
|
88
|
+
]);
|
|
89
|
+
printResult(io, envelope, options.json);
|
|
90
|
+
if (result.failedCount > 0) {
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const message = getErrorMessage(error);
|
|
96
|
+
printResult(io, fail('skill.sync', 'SKILL_SYNC_FAILED', message, { applied: false }, [message]), options.json);
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function registerSkillRegistryCommands(skill, io) {
|
|
101
|
+
addJsonOption(skill
|
|
102
|
+
.command('list')
|
|
103
|
+
.description(LIST_DESCRIPTION)
|
|
104
|
+
.option('--include-internal', LIST_INCLUDE_INTERNAL_HELP)).action((options) => runSkillList(options, io));
|
|
105
|
+
addJsonOption(skill.command('doctor').description(DOCTOR_DESCRIPTION)).action((options) => runSkillDoctor(options, io));
|
|
106
|
+
// Slice #12 final piece (per spec §9 line 1105):
|
|
107
|
+
// `peaks skills sync 8 平台分发`. Idempotent: re-running is a
|
|
108
|
+
// no-op when the symlinks are already correct.
|
|
109
|
+
addJsonOption(skill
|
|
110
|
+
.command('sync')
|
|
111
|
+
.description(SYNC_DESCRIPTION)
|
|
112
|
+
.option('--platform <id>', SYNC_PLATFORM_HELP)
|
|
113
|
+
.option('--all', SYNC_ALL_HELP)
|
|
114
|
+
.option('--dry-run', SYNC_DRY_RUN_HELP)
|
|
115
|
+
.option('--reconcile-junctions', SYNC_RECONCILE_HELP)
|
|
116
|
+
.option('--project <path>', SYNC_PROJECT_HELP)).action((options) => runSkillSyncCommand(options, io));
|
|
117
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { inspectSkillRunbook } from '../../../services/skills/skill-runbook-service.js';
|
|
2
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
3
|
+
import { addJsonOption, getErrorMessage, printResult } from '../../cli-helpers.js';
|
|
4
|
+
export function registerSkillRunbookCommand(skill, io) {
|
|
5
|
+
addJsonOption(skill
|
|
6
|
+
.command('runbook <name>')
|
|
7
|
+
.description('Inspect a skill Default runbook section and its --apply authorization-note status')).action(async (name, options) => {
|
|
8
|
+
try {
|
|
9
|
+
const inspection = await inspectSkillRunbook(name);
|
|
10
|
+
const result = inspection.ok
|
|
11
|
+
? ok('skill.runbook', inspection)
|
|
12
|
+
: fail('skill.runbook', inspection.hasRunbook ? 'SKILL_RUNBOOK_APPLY_UNGATED' : 'SKILL_RUNBOOK_MISSING', inspection.hasRunbook
|
|
13
|
+
? `Skill ${inspection.name} has ${inspection.destructiveApplyLines.length} destructive --apply command(s) without an authorization/dry-run note`
|
|
14
|
+
: `Skill ${inspection.name} is missing a ## Default runbook section`, inspection, inspection.hasRunbook
|
|
15
|
+
? [
|
|
16
|
+
'Add an authorization or --dry-run note next to destructive --apply lines in the runbook section'
|
|
17
|
+
]
|
|
18
|
+
: ['Add a `## Default runbook` section to the skill SKILL.md']);
|
|
19
|
+
printResult(io, result, options.json);
|
|
20
|
+
if (!inspection.ok) {
|
|
21
|
+
process.exitCode = 1;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
printResult(io, fail('skill.runbook', 'SKILL_NOT_FOUND', getErrorMessage(error), { name }), options.json);
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|