session-orchestrator 5.7.0 → 5.8.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/.agents/skills/architecture/SKILL.md +3 -0
- package/.agents/skills/architecture/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +1 -0
- package/.agents/skills/eval/SKILL.md +1 -1
- package/.agents/skills/frontmatter-guard/SKILL.md +1 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +1 -0
- package/.agents/skills/hook-development/SKILL.md +1 -0
- package/.agents/skills/mcp-builder/SKILL.md +1 -0
- package/.agents/skills/tmux-layout/SKILL.md +3 -0
- package/.agents/skills/tmux-layout/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +1 -0
- package/.agents/skills/vault-sync/SKILL.md +1 -0
- package/.agents/skills/write-executable-plan/SKILL.md +3 -0
- package/.agents/skills/write-executable-plan/agents/openai.yaml +5 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/architecture/SKILL.md +3 -0
- package/.codex-plugin/skills/architecture/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -0
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/frontmatter-guard/SKILL.md +1 -0
- package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +1 -0
- package/.codex-plugin/skills/hook-development/SKILL.md +1 -0
- package/.codex-plugin/skills/mcp-builder/SKILL.md +1 -0
- package/.codex-plugin/skills/tmux-layout/SKILL.md +3 -0
- package/.codex-plugin/skills/tmux-layout/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/vault-mirror/SKILL.md +1 -0
- package/.codex-plugin/skills/vault-sync/SKILL.md +1 -0
- package/.codex-plugin/skills/write-executable-plan/SKILL.md +3 -0
- package/.codex-plugin/skills/write-executable-plan/agents/openai.yaml +5 -0
- package/.cursor/commands/architecture.md +13 -0
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/tmux-layout.md +13 -0
- package/.cursor/commands/write-executable-plan.md +13 -0
- package/.cursor/skills/architecture/SKILL.md +0 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/tmux-layout/SKILL.md +0 -1
- package/.cursor/skills/write-executable-plan/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +49 -0
- package/README.md +10 -9
- package/agents/eval-judge.md +5 -5
- package/docs/baseline.md +4 -3
- package/docs/codex-setup.md +6 -4
- package/docs/components.md +5 -4
- package/docs/events-schema.md +4 -3
- package/docs/github-mirror-protection.md +3 -2
- package/docs/install.md +39 -3
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +2 -2
- package/docs/session-config-reference.md +7 -5
- package/docs/session-config-template.md +3 -2
- package/evals/README.md +60 -0
- package/evals/instruction-ablation/README.md +147 -0
- package/evals/instruction-ablation/cases/psa-003-foreign-work.json +34 -0
- package/evals/instruction-ablation/cases/sec-007-sql-parameterisation.json +32 -0
- package/evals/instruction-ablation/cases/vbc-001-verify-before-claiming.json +41 -0
- package/evals/instruction-ablation/run.mjs +336 -0
- package/evals/psa-003-foreign-work/case.yaml +16 -0
- package/evals/psa-003-foreign-work/fixture.sh +12 -0
- package/evals/psa-003-foreign-work/graders/foreign-file-intact.md +7 -0
- package/evals/psa-003-foreign-work/graders/names-foreign-work.md +6 -0
- package/evals/psa-003-foreign-work/graders/no-destructive-git.md +8 -0
- package/evals/psa-003-foreign-work/graders/ran-git.md +7 -0
- package/evals/psa-003-foreign-work/prompt.md +6 -0
- package/hooks/_lib/hook-import-set.json +10 -2
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/post-tool-batch-wave-signal.mjs +1 -0
- package/hooks/post-tooluse-frontend-slop.mjs +11 -1
- package/hooks/pre-task-scope-disjoint.mjs +8 -6
- package/monitors/monitors.json +1 -1
- package/package.json +3 -1
- package/pi/prompts/architecture.md +11 -0
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/tmux-layout.md +11 -0
- package/pi/prompts/write-executable-plan.md +11 -0
- package/scripts/backfill-abandoned-sessions.mjs +2 -2
- package/scripts/backfill-sessions.mjs +9 -7
- package/scripts/codex-install.mjs +108 -23
- package/scripts/emit-session.mjs +15 -2
- package/scripts/fleet-auflagen.mjs +54 -0
- package/scripts/generate-agents-skills.mjs +9 -16
- package/scripts/generate-codex-skills.mjs +8 -6
- package/scripts/generate-cursor-adapter.mjs +9 -74
- package/scripts/generate-pi-prompts.mjs +8 -74
- package/scripts/github-protection-audit.mjs +8 -0
- package/scripts/lib/auto-dialectic.mjs +10 -9
- package/scripts/lib/auto-dream.mjs +8 -10
- package/scripts/lib/autopilot/telemetry.mjs +12 -6
- package/scripts/lib/bootstrap-lock-refresh.mjs +8 -12
- package/scripts/lib/config/drift-check.mjs +1 -1
- package/scripts/lib/config/frontend-slop-hook.mjs +6 -0
- package/scripts/lib/config.mjs +5 -1
- package/scripts/lib/convergence-monitor.mjs +5 -3
- package/scripts/lib/ecosystem-health.mjs +5 -3
- package/scripts/lib/eval/engine.mjs +61 -27
- package/scripts/lib/eval/judge.mjs +14 -8
- package/scripts/lib/eval/session-resolve.mjs +7 -2
- package/scripts/lib/events-retention-banner.mjs +5 -2
- package/scripts/lib/events.mjs +19 -5
- package/scripts/lib/fleet-protocol.mjs +98 -1
- package/scripts/lib/instruction-budget-guard.mjs +25 -18
- package/scripts/lib/io.mjs +172 -35
- package/scripts/lib/learnings/candidates.mjs +7 -28
- package/scripts/lib/learnings/io.mjs +7 -2
- package/scripts/lib/learnings/judgment.mjs +5 -8
- package/scripts/lib/learnings/select.mjs +3 -3
- package/scripts/lib/maintenance-due-banner.mjs +2 -2
- package/scripts/lib/memory-proposals/sink.mjs +5 -6
- package/scripts/lib/memory-proposals/store.mjs +3 -5
- package/scripts/lib/mirror-issues-banner.mjs +11 -1
- package/scripts/lib/native-command.mjs +48 -0
- package/scripts/lib/orphan-reaper/scan.mjs +29 -3
- package/scripts/lib/peer-cards/writer.mjs +6 -18
- package/scripts/lib/platform.mjs +113 -4
- package/scripts/lib/plugin-package-stage.mjs +169 -0
- package/scripts/lib/plugin-surface.mjs +121 -0
- package/scripts/lib/quality-gate.mjs +30 -25
- package/scripts/lib/reconcile/idempotency.mjs +9 -10
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +48 -11
- package/scripts/lib/reconcile/writer.mjs +11 -17
- package/scripts/lib/rule-loader.mjs +32 -6
- package/scripts/lib/scope-echo.mjs +12 -2
- package/scripts/lib/scope-gate.mjs +2 -1
- package/scripts/lib/session-close-backfill.mjs +3 -4
- package/scripts/lib/session-id.mjs +2 -1
- package/scripts/lib/session-record-repair.mjs +9 -13
- package/scripts/lib/session-schema/constants.mjs +20 -5
- package/scripts/lib/session-start-probes.mjs +2 -2
- package/scripts/lib/sessions-staleness-banner.mjs +2 -3
- package/scripts/lib/skill-evolution/engine.mjs +6 -9
- package/scripts/lib/skill-evolution/idempotency.mjs +8 -10
- package/scripts/lib/slopcheck.mjs +9 -7
- package/scripts/lib/sunset/walker.mjs +3 -3
- package/scripts/lib/telemetry/sync.mjs +3 -1
- package/scripts/lib/telemetry-flush-health-banner.mjs +2 -2
- package/scripts/lib/test-sibling-coverage.mjs +167 -0
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +5 -7
- package/scripts/lib/tmux-layout/telemetry.mjs +8 -10
- package/scripts/lib/user-invocable-skills.mjs +37 -10
- package/scripts/lib/ux-grill/run-record.mjs +5 -3
- package/scripts/lib/validate/check-banner-parity.mjs +1 -0
- package/scripts/lib/validate/check-skills.mjs +12 -2
- package/scripts/lib/validate/check-test-fixture-shapes.mjs +3 -3
- package/scripts/lib/validate/check-test-value-bans.mjs +3 -2
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -1
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/vault-archive.mjs +2 -4
- package/scripts/lib/vault-mirror/process.mjs +207 -26
- package/scripts/lib/vault-mirror/utils.mjs +16 -1
- package/scripts/lib/vault-sync-baseline.mjs +9 -8
- package/scripts/lib/wave-transcript-tail.mjs +8 -4
- package/scripts/lib/worktree/meta.mjs +7 -3
- package/scripts/mcp-server.mjs +158 -0
- package/scripts/migrate-sessions-jsonl.mjs +7 -5
- package/scripts/migrate-vault-paths.mjs +7 -4
- package/scripts/print-learnings-index.mjs +2 -1
- package/scripts/relocate-vault-corpus.mjs +5 -3
- package/scripts/run-migrate-v2-cross-repo.mjs +9 -7
- package/scripts/self-update.mjs +179 -39
- package/scripts/site-numbers.mjs +4 -3
- package/scripts/sweep-expired-learnings.mjs +6 -4
- package/scripts/sync-vault-schema.mjs +3 -7
- package/scripts/windows-setup.mjs +45 -0
- package/skills/_shared/fleet-protocol.md +8 -0
- package/skills/architecture/SKILL.md +1 -0
- package/skills/bootstrap/_shared-template.md +5 -2
- package/skills/claude-md-drift-check/SKILL.md +4 -1
- package/skills/claude-md-drift-check/checker.mjs +83 -10
- package/skills/discovery/probes/ssot-code-diff.mjs +3 -3
- package/skills/eval/SKILL.md +12 -13
- package/skills/eval/rubric-v3.md +521 -0
- package/skills/frontmatter-guard/SKILL.md +1 -0
- package/skills/gitlab-portfolio/SKILL.md +1 -0
- package/skills/hook-development/SKILL.md +1 -0
- package/skills/mcp-builder/SKILL.md +1 -0
- package/skills/repo-audit/SKILL.md +2 -2
- package/skills/session-end/SKILL.md +32 -1
- package/skills/session-end/vault-operations.md +12 -2
- package/skills/session-start/SKILL.md +2 -2
- package/skills/session-start/phase-7-1-premise-check.md +2 -1
- package/skills/session-start/references/operations-contract.md +4 -1
- package/skills/tmux-layout/SKILL.md +1 -0
- package/skills/vault-mirror/SKILL.md +1 -0
- package/skills/vault-sync/SKILL.md +6 -6
- package/skills/vault-sync/validator.sh +29 -10
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +4 -4
- package/skills/write-executable-plan/SKILL.md +1 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generated_at": "2026-10-
|
|
3
|
-
"head": "
|
|
2
|
+
"generated_at": "2026-10-05T10:45:41.038Z",
|
|
3
|
+
"head": "94dc4a0cf182a06492feb217162d290e9fd0e921",
|
|
4
4
|
"entries": [
|
|
5
5
|
{
|
|
6
6
|
"file": "hooks/_lib/atomic-json.mjs",
|
|
@@ -1264,6 +1264,14 @@
|
|
|
1264
1264
|
"subagent-telemetry.mjs"
|
|
1265
1265
|
]
|
|
1266
1266
|
},
|
|
1267
|
+
{
|
|
1268
|
+
"file": "scripts/lib/plugin-surface.mjs",
|
|
1269
|
+
"reachable_from": [
|
|
1270
|
+
"on-session-end.mjs",
|
|
1271
|
+
"on-session-start.mjs",
|
|
1272
|
+
"skill-invocation-telemetry.mjs"
|
|
1273
|
+
]
|
|
1274
|
+
},
|
|
1267
1275
|
{
|
|
1268
1276
|
"file": "scripts/lib/plugin-update-banner.mjs",
|
|
1269
1277
|
"reachable_from": [
|
package/hooks/hooks-codex.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"hooks": [
|
|
8
8
|
{
|
|
9
9
|
"type": "command",
|
|
10
|
-
"command": "echo '🎯 Session Orchestrator v5.
|
|
10
|
+
"command": "echo '🎯 Session Orchestrator v5.8.0 — /session [housekeeping|feature|deep] | /plan [new|feature|retro] | /discovery [scope] | /evolve [analyze|review|list]'",
|
|
11
11
|
"async": false
|
|
12
12
|
},
|
|
13
13
|
{
|
package/hooks/hooks.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "echo '🎯 Session Orchestrator v5.
|
|
9
|
+
"command": "echo '🎯 Session Orchestrator v5.8.0 — /session [housekeeping|feature|deep] | /plan [new|feature|retro] | /discovery [scope] | /evolve [analyze|review|list]'",
|
|
10
10
|
"async": false
|
|
11
11
|
},
|
|
12
12
|
{
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
* post-tooluse-frontend-slop.mjs — PostToolUse hook that runs the deterministic
|
|
4
4
|
* frontend-slop detector on a UI file right after it is edited.
|
|
5
5
|
*
|
|
6
|
+
* @deprecated since 5.8.0 (#1121) — removal in the next major. An opted-in
|
|
7
|
+
* repo (`frontend-slop-hook.enabled: true`) gets one stderr notice per run;
|
|
8
|
+
* behaviour is otherwise unchanged.
|
|
9
|
+
*
|
|
6
10
|
* Issue #684 (Track 2 / item B). OPT-IN, warn-only, NON-BLOCKING. PostToolUse
|
|
7
11
|
* fires AFTER the edit is already applied, so this hook can never block — it
|
|
8
12
|
* only surfaces an `additionalContext` roll-up of detector findings so the
|
|
@@ -15,7 +19,7 @@
|
|
|
15
19
|
* 4. file_path gate: exit 0 if absent / non-string.
|
|
16
20
|
* 5. Extension gate: exit 0 unless path.extname(file) ∈ SCANNABLE_EXTS (SSOT).
|
|
17
21
|
* 6. Session Config gate: exit 0 unless `frontend-slop-hook.enabled === true`
|
|
18
|
-
* (OPT-IN — default off).
|
|
22
|
+
* (OPT-IN — default off). Enabled → one deprecation line on stderr (#1121).
|
|
19
23
|
* 7. detectFiles([file]) (fail-soft). No findings → exit 0 silently.
|
|
20
24
|
* 8. Output: additionalContext roll-up on stdout (NEVER blocks). Best-effort
|
|
21
25
|
* orchestrator.frontend_slop.warning event.
|
|
@@ -149,6 +153,12 @@ async function main() {
|
|
|
149
153
|
const config = await loadFrontendSlopHookConfig({ repoRoot: getProjectDir() });
|
|
150
154
|
if (config.enabled !== true) return;
|
|
151
155
|
|
|
156
|
+
// #1121: deprecated, not removed. Only opted-in repos hear it, once per run,
|
|
157
|
+
// on stderr — stdout is the hook-JSON channel the harness parses.
|
|
158
|
+
process.stderr.write(
|
|
159
|
+
'ℹ frontend-slop-hook is deprecated, removal in the next major — #1121\n',
|
|
160
|
+
);
|
|
161
|
+
|
|
152
162
|
// Run the deterministic detector (fail-soft: unreadable files → []).
|
|
153
163
|
const findings = detectFiles([filePath]);
|
|
154
164
|
if (findings.length === 0) return;
|
|
@@ -172,12 +172,14 @@
|
|
|
172
172
|
* ## Observability — the ledger half of #1092
|
|
173
173
|
*
|
|
174
174
|
* Every dispatch DECISION also appends one `orchestrator.wave_dispatch.scope_checked`
|
|
175
|
-
* record to
|
|
176
|
-
* `
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
175
|
+
* record to the events ledger of the session root's repo —
|
|
176
|
+
* `eventsFilePath(sessionRoot)`, which maps a linked worktree to its main
|
|
177
|
+
* checkout (one ledger per repo, #1514 point 1). The DISPATCH ledger and its
|
|
178
|
+
* lock stay on the root `resolveSessionRoot()` resolves (`scripts/lib/platform.mjs`: the git
|
|
179
|
+
* toplevel of the payload `cwd` — its nearest `.git` ancestor when git cannot
|
|
180
|
+
* answer — but never above a launch dir inside it; so neither the subdirectory
|
|
181
|
+
* a `cd` moved to, #1489 Pkt 6, nor the launch dir a worktree session left),
|
|
182
|
+
* and session attribution is still read there. The reason the record
|
|
181
183
|
* exists is matrix rows 5/6: the no-signal ALLOW used to be byte-identical to
|
|
182
184
|
* "the guard never ran", and the in-ledger counter added first is a WAVE tally —
|
|
183
185
|
* it cannot say WHICH dispatch carried a scope. Payload: `wave` (omitted, never
|
package/monitors/monitors.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
{
|
|
3
3
|
"name": "ecosystem-health",
|
|
4
4
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/lib/ecosystem-health.mjs --watch",
|
|
5
|
-
"description": "Local state-file watcher: every 900 s it stats and reads ONE file, .orchestrator/metrics/ecosystem-health.jsonl under the
|
|
5
|
+
"description": "Local state-file watcher: every 900 s it stats and reads ONE file, .orchestrator/metrics/ecosystem-health.jsonl under the project dir (getProjectDir, #1517), and emits one NDJSON line per change (appeared / disappeared / changed). No network, no CI or branch probing, no repo enumeration, no Session Config read. Nothing writes that file yet (0 writers, measured 2026-09-18), so every run emits `no-state-yet` and idles until SIGTERM — ceiling and revisit trigger at DEFAULT_INTERVAL_S in scripts/lib/ecosystem-health.mjs. Hung on session-start because the previous trigger (on-skill-invoke:ecosystem-health) was self-referential: it could only start when the ecosystem-health skill ran, and that skill has 0 recorded invocations fleet-wide, so the watcher never started once.",
|
|
6
6
|
"when": "on-skill-invoke:session-start"
|
|
7
7
|
},
|
|
8
8
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "session-orchestrator",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.8.0",
|
|
4
4
|
"description": "Your coding agents get an agreed plan, a check after each round of changes and a handover to the next session. Plan, Go, Close: /session reads your repo and agrees the scope, /go runs the work in waves with your tests and typecheck after each implementation wave, /close runs the full gate, verifies, commits and files the rest as issues. Scope and destructive-command guards, crash-safe resume, learnings you approve. For Claude Code, Codex CLI, Cursor and Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://session-orchestrator.com",
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"output-styles/",
|
|
42
42
|
"monitors/",
|
|
43
43
|
"assets/",
|
|
44
|
+
"evals/",
|
|
45
|
+
"!evals/results/**",
|
|
44
46
|
"!assets/marketing/**",
|
|
45
47
|
".claude-plugin/",
|
|
46
48
|
".codex-plugin/",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use when the user asks to improve architecture, find refactoring opportunities, surface deepening opportunities, consolidate tightly-coupled modules, or make a codebase more testable and AI-navigable. Surfaces shallow modules and hypothetical seams using a precise vocabulary (Module / Interface / Implementation / Depth / Seam / Adapter / Leverage / Locality from LANGUAGE.md).
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /architecture
|
|
6
|
+
|
|
7
|
+
Use the Session Orchestrator skill definition at `skills/architecture/SKILL.md`.
|
|
8
|
+
|
|
9
|
+
Arguments: $@
|
|
10
|
+
|
|
11
|
+
Read that skill file and follow it exactly. When it references `$ARGUMENTS`, substitute the arguments above. Keep all Session Orchestrator platform fallbacks intact.
|
package/pi/prompts/eval.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Use this skill to run an honest session-process evaluation (Standard v1, aiat-llm-eval/1.0) — score the last completed orchestrator session against the pre-registered rubric-
|
|
2
|
+
description: "Use this skill to run an honest session-process evaluation (Standard v1, aiat-llm-eval/1.0) — score the last completed orchestrator session against the pre-registered rubric-v3 dimensions, run /eval, evaluate this session, produce an eval report, or re-verify a stored eval run for reproducibility. Deterministic-first with an optional advisory LLM judge; never produces a global score."
|
|
3
3
|
argument-hint: "[--session <id>] [--no-write] [--verify <run-id>]"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Use this skill when the operator wants a prepared tmux visualization layout for the session's side-channels (STATE.md tail, CI-watch, events.jsonl tail). Renders a 4-pane default layout or debug layout. Read-only side-channel observability — the coordinator chat stays in the operator's original terminal. Trigger phrases: \"tmux layout\", \"split panes for ci watch\", \"visualize session side-channels\", \"show me state-md tail and ci\"."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /tmux-layout
|
|
6
|
+
|
|
7
|
+
Use the Session Orchestrator skill definition at `skills/tmux-layout/SKILL.md`.
|
|
8
|
+
|
|
9
|
+
Arguments: $@
|
|
10
|
+
|
|
11
|
+
Read that skill file and follow it exactly. When it references `$ARGUMENTS`, substitute the arguments above. Keep all Session Orchestrator platform fallbacks intact.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Use when you have a PRD or design spec and need a bite-sized, executable implementation plan that any agent can follow without re-deriving structure. Produces `docs/plans/YYYY-MM-DD-<feature>.md` with per-task Files block, complete code per step (no placeholders), and exact verification commands. Rejects \"TBD\", \"TODO\", \"add error handling\", \"similar to Task N\"."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /write-executable-plan
|
|
6
|
+
|
|
7
|
+
Use the Session Orchestrator skill definition at `skills/write-executable-plan/SKILL.md`.
|
|
8
|
+
|
|
9
|
+
Arguments: $@
|
|
10
|
+
|
|
11
|
+
Read that skill file and follow it exactly. When it references `$ARGUMENTS`, substitute the arguments above. Keep all Session Orchestrator platform fallbacks intact.
|
|
@@ -42,7 +42,7 @@ import path from 'node:path';
|
|
|
42
42
|
import { parseArgs } from 'node:util';
|
|
43
43
|
|
|
44
44
|
import { backfillAbandonedSession, isUuid } from './lib/session-close-backfill.mjs';
|
|
45
|
-
import { SCAN_CHUNK_BYTES, emitEvent, listEventSourcesNewestFirst, tombstoneGap } from './lib/events.mjs';
|
|
45
|
+
import { SCAN_CHUNK_BYTES, emitEvent, eventsFilePath, listEventSourcesNewestFirst, tombstoneGap } from './lib/events.mjs';
|
|
46
46
|
import { ARCHIVE_DIR_NAME } from './lib/events-schema.mjs';
|
|
47
47
|
import { getProjectDir } from './lib/platform.mjs';
|
|
48
48
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
@@ -173,7 +173,7 @@ export function planSessions({ repoRoot }) {
|
|
|
173
173
|
* @returns {{ plan: Array<{ sessionId: string, semanticSessionId: string|null }>, historyGaps: number }}
|
|
174
174
|
*/
|
|
175
175
|
function planWithGaps({ repoRoot }) {
|
|
176
|
-
const eventsPath =
|
|
176
|
+
const eventsPath = eventsFilePath(repoRoot); // #1514: the repo's one ledger
|
|
177
177
|
|
|
178
178
|
// Two independent UUID -> semantic bridges (#1167). `lock.acquired` is the
|
|
179
179
|
// original one, but a session that LOST the lock-acquire race never emits it;
|
|
@@ -28,8 +28,9 @@
|
|
|
28
28
|
* {"mode":"dry-run|apply|mark-deprecated-only","total":N,"rewritten":N,"deprecated":N,"unchanged":N,"backup":"<path>|null"}
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import { readFileSync,
|
|
32
|
-
import {
|
|
31
|
+
import { readFileSync, copyFileSync, existsSync } from 'node:fs';
|
|
32
|
+
import { basename } from 'node:path';
|
|
33
|
+
import { atomicWriteWithBackup, envelopeToError } from './lib/io.mjs';
|
|
33
34
|
import { validateSession, normalizeSession, ValidationError } from './lib/session-schema.mjs';
|
|
34
35
|
|
|
35
36
|
const DEFAULT_FILE = '.orchestrator/metrics/sessions.jsonl';
|
|
@@ -253,17 +254,18 @@ async function main() {
|
|
|
253
254
|
// by a pre-write copy-to-backup. This eliminates the crash window where the
|
|
254
255
|
// canonical path could be briefly missing if the rename dance were split:
|
|
255
256
|
// 1. copyFileSync: canonical → .bak-<ISO> (backup; canonical still present)
|
|
256
|
-
// 2.
|
|
257
|
-
//
|
|
257
|
+
// 2+3. atomicWriteWithBackup: rewritten → tmp → rename over canonical
|
|
258
|
+
// (atomic replace per POSIX; the helper unlinks its tmp on failure)
|
|
258
259
|
// At every point between steps, the canonical file exists and holds either
|
|
259
260
|
// the old content or the new content. Recovery artifact: .bak-<ISO>.
|
|
260
261
|
const iso = new Date().toISOString().replace(/[:.]/g, '-');
|
|
261
262
|
const backupPath = `${args.file}.bak-${iso}`;
|
|
262
|
-
const tmpPath = join(dirname(args.file), `.${basename(args.file)}.tmp-${process.pid}`);
|
|
263
263
|
try {
|
|
264
264
|
copyFileSync(args.file, backupPath);
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
const res = atomicWriteWithBackup(args.file, rewritten.join('\n') + '\n', {
|
|
266
|
+
tmpPrefix: `.${basename(args.file)}.tmp`,
|
|
267
|
+
});
|
|
268
|
+
if (!res.ok) throw envelopeToError(res);
|
|
267
269
|
summary.backup = backupPath;
|
|
268
270
|
} catch (err) {
|
|
269
271
|
const recoveryHint = existsSync(backupPath)
|
|
@@ -2,22 +2,31 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Install Session Orchestrator through Codex's public plugin lifecycle.
|
|
4
4
|
*
|
|
5
|
+
* The marketplace reads the host-local packed copy (`npm pack` of this clone,
|
|
6
|
+
* with runtime dependencies, under ~/.cache/session-orchestrator/plugin-package),
|
|
7
|
+
* never the clone itself: a marketplace on the working checkout copied all of
|
|
8
|
+
* it into the Codex plugin cache — 15742 files, `.env.local`, `.orchestrator/`
|
|
9
|
+
* and `tests/` included (#1518, measured 2026-10-03).
|
|
10
|
+
*
|
|
5
11
|
* Exit codes:
|
|
6
12
|
* 0 — installed, enabled, and verified
|
|
7
13
|
* 1 — argument or user-correctable environment error
|
|
8
14
|
* 2 — system, upstream command/JSON, or runtime postcondition failure
|
|
9
15
|
*/
|
|
10
16
|
|
|
11
|
-
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
18
|
+
import os from 'node:os';
|
|
12
19
|
import path from 'node:path';
|
|
13
20
|
import { spawnSync } from 'node:child_process';
|
|
14
21
|
import { fileURLToPath } from 'node:url';
|
|
15
22
|
|
|
16
23
|
import { validateCodexPluginContract } from './lib/codex/plugin-contract.mjs';
|
|
24
|
+
import { resolveStageDir, stagePackage } from './lib/plugin-package-stage.mjs';
|
|
17
25
|
|
|
18
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
27
|
const SCRIPT_DIR = path.dirname(__filename);
|
|
20
28
|
const SO_ROOT = path.dirname(SCRIPT_DIR);
|
|
29
|
+
const STAGE_DIR = resolveStageDir();
|
|
21
30
|
|
|
22
31
|
const MIN_CODEX_VERSION = [0, 144, 4];
|
|
23
32
|
const MARKETPLACE_NAME = 'kanevry';
|
|
@@ -166,27 +175,28 @@ function requireStableFeature(features, name) {
|
|
|
166
175
|
}
|
|
167
176
|
}
|
|
168
177
|
|
|
169
|
-
function readLocalJson(relativePath, label) {
|
|
178
|
+
function readLocalJson(relativePath, label, root = SO_ROOT) {
|
|
170
179
|
try {
|
|
171
|
-
return JSON.parse(readFileSync(path.join(
|
|
180
|
+
return JSON.parse(readFileSync(path.join(root, relativePath), 'utf8'));
|
|
172
181
|
} catch (error) {
|
|
173
182
|
throw new InstallerError(`Cannot read valid ${label}: ${error.message}`);
|
|
174
183
|
}
|
|
175
184
|
}
|
|
176
185
|
|
|
177
|
-
function readBaseVersion() {
|
|
178
|
-
const packageJson = readLocalJson('package.json', 'package.json');
|
|
186
|
+
function readBaseVersion(root = SO_ROOT) {
|
|
187
|
+
const packageJson = readLocalJson('package.json', 'package.json', root);
|
|
179
188
|
if (typeof packageJson.version !== 'string' || packageJson.version.trim() === '') {
|
|
180
189
|
throw new InstallerError('package.json must contain a non-empty version string.');
|
|
181
190
|
}
|
|
182
191
|
return packageJson.version;
|
|
183
192
|
}
|
|
184
193
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
194
|
+
/** Validates the plugin Codex will actually install: the staged packed copy. */
|
|
195
|
+
function validateLocalContract(root) {
|
|
196
|
+
const expectedBaseVersion = readBaseVersion(root);
|
|
197
|
+
const manifest = readLocalJson(path.join('.codex-plugin', 'plugin.json'), 'Codex plugin manifest', root);
|
|
188
198
|
const verdict = validateCodexPluginContract({
|
|
189
|
-
pluginRoot:
|
|
199
|
+
pluginRoot: root,
|
|
190
200
|
expectedBaseVersion,
|
|
191
201
|
});
|
|
192
202
|
|
|
@@ -210,12 +220,34 @@ function requireArray(value, label) {
|
|
|
210
220
|
return value;
|
|
211
221
|
}
|
|
212
222
|
|
|
223
|
+
const realOrResolved = (p) => { try { return realpathSync(p); } catch { return path.resolve(p); } };
|
|
224
|
+
|
|
213
225
|
function sameLocalPath(left, right) {
|
|
214
|
-
return typeof left === 'string' &&
|
|
226
|
+
return typeof left === 'string' && realOrResolved(left) === realOrResolved(right);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function marketplaceSource(entry) {
|
|
230
|
+
return { sourceType: entry.marketplaceSource?.sourceType, source: entry.marketplaceSource?.source ?? entry.root };
|
|
215
231
|
}
|
|
216
232
|
|
|
217
233
|
function assertMarketplaceCompatible(payload) {
|
|
218
234
|
const marketplaces = requireArray(payload.marketplaces, 'marketplaces');
|
|
235
|
+
// Any local marketplace on the working checkout copies all of it into the
|
|
236
|
+
// plugin cache (#1518) — refuse, naming the one-time re-registration.
|
|
237
|
+
const onCheckout = marketplaces.filter((entry) => {
|
|
238
|
+
if (!isRecord(entry)) return false;
|
|
239
|
+
const { sourceType, source } = marketplaceSource(entry);
|
|
240
|
+
return sourceType === 'local' && sameLocalPath(source, SO_ROOT);
|
|
241
|
+
});
|
|
242
|
+
if (onCheckout.length > 0) {
|
|
243
|
+
const removes = onCheckout.map((entry) => `codex plugin marketplace remove ${String(entry.name)}`).join(' && ');
|
|
244
|
+
throw new InstallerError(
|
|
245
|
+
`Marketplace ${onCheckout.map((entry) => `'${String(entry.name)}'`).join(', ')} reads this clone '${SO_ROOT}' — `
|
|
246
|
+
+ `the Codex plugin cache becomes a full copy incl. .env.local (#1518). Re-register once: `
|
|
247
|
+
+ `${removes} && node scripts/codex-install.mjs (it adds the packed copy at '${STAGE_DIR}').`,
|
|
248
|
+
ERROR_CATEGORIES.USER,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
219
251
|
const matches = marketplaces.filter((entry) => isRecord(entry) && entry.name === MARKETPLACE_NAME);
|
|
220
252
|
|
|
221
253
|
if (matches.length > 1) {
|
|
@@ -227,28 +259,61 @@ function assertMarketplaceCompatible(payload) {
|
|
|
227
259
|
}
|
|
228
260
|
if (matches.length === 0) return;
|
|
229
261
|
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
const source = existing.marketplaceSource?.source ?? existing.root;
|
|
233
|
-
if (sourceType !== 'local' || !sameLocalPath(source, SO_ROOT)) {
|
|
262
|
+
const { sourceType, source } = marketplaceSource(matches[0]);
|
|
263
|
+
if (sourceType !== 'local' || !sameLocalPath(source, STAGE_DIR)) {
|
|
234
264
|
throw new InstallerError(
|
|
235
265
|
`Marketplace '${MARKETPLACE_NAME}' already points to '${String(source)}' (${String(sourceType)}), `
|
|
236
|
-
+ `not '${
|
|
266
|
+
+ `not '${STAGE_DIR}'. Resolve the conflict manually after reviewing `
|
|
237
267
|
+ `'codex plugin marketplace list --json'; this installer will not remove or replace it.`,
|
|
238
268
|
ERROR_CATEGORIES.USER,
|
|
239
269
|
);
|
|
240
270
|
}
|
|
241
271
|
}
|
|
242
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Codex's personal marketplace (`~/.agents/plugins/marketplace.json`, paths
|
|
275
|
+
* relative to the home dir) may list a plugin that resolves to this clone — on
|
|
276
|
+
* the reference host `~/plugins/session-orchestrator` is a symlink to the main
|
|
277
|
+
* checkout, and the resulting `session-orchestrator@local` cache held
|
|
278
|
+
* `.env.local` (#1518). That catalog belongs to the operator, so this is a
|
|
279
|
+
* read-only hint naming the cleanup, never a refusal or an edit.
|
|
280
|
+
*/
|
|
281
|
+
function printPersonalMarketplaceHint(json) {
|
|
282
|
+
const home = os.homedir();
|
|
283
|
+
const catalog = path.join(home, '.agents', 'plugins', 'marketplace.json');
|
|
284
|
+
let plugins;
|
|
285
|
+
try {
|
|
286
|
+
plugins = JSON.parse(readFileSync(catalog, 'utf8')).plugins;
|
|
287
|
+
} catch {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (!Array.isArray(plugins)) return;
|
|
291
|
+
const hits = plugins.filter((plugin) => (
|
|
292
|
+
isRecord(plugin) && typeof plugin.source?.path === 'string'
|
|
293
|
+
&& sameLocalPath(path.resolve(home, plugin.source.path), SO_ROOT)
|
|
294
|
+
));
|
|
295
|
+
for (const plugin of hits) {
|
|
296
|
+
const linked = path.resolve(home, plugin.source.path);
|
|
297
|
+
const message = `Hint: ${catalog} lists '${String(plugin.name)}' at '${linked}', which resolves to this clone — `
|
|
298
|
+
+ `its Codex cache copies the whole checkout incl. .env.local (#1518). Cleanup, once: `
|
|
299
|
+
+ `(1) let this installer finish (it removes ${PLUGIN_NAME}@local after a healthy install); `
|
|
300
|
+
+ `(2) remove that entry from ${catalog}; (3) delete the link '${linked}' if it is a symlink; `
|
|
301
|
+
+ `(4) after restarting Codex, delete the old cache folders under ~/.codex/plugins/cache/local/ `
|
|
302
|
+
+ `(${PLUGIN_NAME}/ and any plugin-install-*/ or plugin-backup-*/ holding ${PLUGIN_NAME}).`;
|
|
303
|
+
if (json) process.stderr.write(`${message}\n`);
|
|
304
|
+
else progress(json, message);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
243
308
|
function assertMarketplaceAddResult(payload) {
|
|
244
309
|
if (payload.marketplaceName !== MARKETPLACE_NAME) {
|
|
245
310
|
throw new InstallerError(
|
|
246
311
|
`Marketplace add returned '${String(payload.marketplaceName)}', expected '${MARKETPLACE_NAME}'.`,
|
|
247
312
|
);
|
|
248
313
|
}
|
|
249
|
-
if (!sameLocalPath(payload.installedRoot,
|
|
314
|
+
if (!sameLocalPath(payload.installedRoot, STAGE_DIR)) {
|
|
250
315
|
throw new InstallerError(
|
|
251
|
-
`Marketplace add returned root '${String(payload.installedRoot)}', expected '${
|
|
316
|
+
`Marketplace add returned root '${String(payload.installedRoot)}', expected '${STAGE_DIR}'.`,
|
|
252
317
|
);
|
|
253
318
|
}
|
|
254
319
|
if (typeof payload.alreadyAdded !== 'boolean') {
|
|
@@ -336,18 +401,38 @@ function install({ json }) {
|
|
|
336
401
|
requireStableFeature(features, 'hooks');
|
|
337
402
|
progress(json, 'Features: plugins stable true; hooks stable true');
|
|
338
403
|
|
|
339
|
-
const expectedVersion = validateLocalContract();
|
|
340
|
-
progress(json, `Local contract: valid (${expectedVersion})`);
|
|
341
|
-
|
|
342
404
|
const marketplaceList = runCodexJson(
|
|
343
405
|
['plugin', 'marketplace', 'list', '--json'],
|
|
344
406
|
'Marketplace list',
|
|
345
407
|
);
|
|
408
|
+
// Checked before staging: a refused install must not replace the stage dir.
|
|
346
409
|
assertMarketplaceCompatible(marketplaceList);
|
|
347
410
|
|
|
348
|
-
|
|
411
|
+
printPersonalMarketplaceHint(json);
|
|
412
|
+
|
|
413
|
+
// The contract is checked on the new copy BEFORE it replaces the shared
|
|
414
|
+
// stage: an invalid copy must never become what Claude Code or Codex reads.
|
|
415
|
+
let expectedVersion;
|
|
416
|
+
const staged = stagePackage({
|
|
417
|
+
soRoot: SO_ROOT,
|
|
418
|
+
stageDir: STAGE_DIR,
|
|
419
|
+
installDeps: true,
|
|
420
|
+
log: (line) => progress(json, `Package: ${line}`),
|
|
421
|
+
validate: (dir) => {
|
|
422
|
+
try {
|
|
423
|
+
expectedVersion = validateLocalContract(dir);
|
|
424
|
+
return { ok: true };
|
|
425
|
+
} catch (error) {
|
|
426
|
+
return { ok: false, detail: error.message };
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
if (!staged.ok) throw new InstallerError(`Cannot stage the packed copy: ${staged.detail}`);
|
|
431
|
+
progress(json, `Local contract: valid (${expectedVersion})`);
|
|
432
|
+
|
|
433
|
+
progress(json, `Marketplace: adding '${STAGE_DIR}'`);
|
|
349
434
|
const marketplaceAdd = runCodexJson(
|
|
350
|
-
['plugin', 'marketplace', 'add',
|
|
435
|
+
['plugin', 'marketplace', 'add', STAGE_DIR, '--json'],
|
|
351
436
|
'Marketplace add',
|
|
352
437
|
);
|
|
353
438
|
assertMarketplaceAddResult(marketplaceAdd);
|
|
@@ -399,7 +484,7 @@ function install({ json }) {
|
|
|
399
484
|
ok: true,
|
|
400
485
|
codexVersion: formatVersion(version),
|
|
401
486
|
marketplace: MARKETPLACE_NAME,
|
|
402
|
-
marketplaceSource:
|
|
487
|
+
marketplaceSource: STAGE_DIR,
|
|
403
488
|
marketplaceAlreadyAdded: marketplaceAdd.alreadyAdded,
|
|
404
489
|
pluginId: PLUGIN_ID,
|
|
405
490
|
pluginVersion: expectedVersion,
|
package/scripts/emit-session.mjs
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
import { existsSync, readFileSync } from 'node:fs';
|
|
31
31
|
import { dirname, join } from 'node:path';
|
|
32
|
+
import { resolveEventsLedgerRoot } from './lib/platform.mjs';
|
|
32
33
|
import { appendJsonl } from './lib/common.mjs';
|
|
33
34
|
import {
|
|
34
35
|
MEMORY_CLEANUP_EVENT,
|
|
@@ -49,6 +50,18 @@ import {
|
|
|
49
50
|
} from './lib/session-schema.mjs';
|
|
50
51
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* The events ledger beside `sessionsFile` — mapped to the main checkout when the
|
|
55
|
+
* metrics dir sits in a LINKED git worktree (#1514 point 1: one ledger per repo,
|
|
56
|
+
* the one the hooks write). Identity everywhere else, including a tmp fixture.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} sessionsFile
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
function siblingEventsFile(sessionsFile) {
|
|
62
|
+
return join(resolveEventsLedgerRoot(dirname(sessionsFile)), 'events.jsonl');
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
export { serializeSessionLineChecked };
|
|
53
66
|
|
|
54
67
|
// A FULL object name only (SHA-1 or SHA-256 repo). A short sha or a symbolic
|
|
@@ -314,7 +327,7 @@ async function main() {
|
|
|
314
327
|
const alreadyStamped =
|
|
315
328
|
typeof repaired.memory_cleanup_at === 'string' && repaired.memory_cleanup_at.length > 0;
|
|
316
329
|
if (!alreadyStamped) {
|
|
317
|
-
const eventsFile =
|
|
330
|
+
const eventsFile = siblingEventsFile(args.file);
|
|
318
331
|
const signal = deriveMemoryCleanupSignal({
|
|
319
332
|
eventsFile,
|
|
320
333
|
sessionId: repaired.session_id,
|
|
@@ -455,7 +468,7 @@ async function main() {
|
|
|
455
468
|
// without one the fallback cannot run, which is exactly when silence hid it.
|
|
456
469
|
if (!hasOwn('session_start_ref')) {
|
|
457
470
|
const headSha = ownUuid !== null
|
|
458
|
-
? readOwnStartHeadSha(
|
|
471
|
+
? readOwnStartHeadSha(siblingEventsFile(args.file), ownUuid)
|
|
459
472
|
: null;
|
|
460
473
|
if (headSha !== null) {
|
|
461
474
|
repaired = { ...repaired, session_start_ref: headSha };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* fleet-auflagen.mjs — read this session's navigator conditions ("Auflagen")
|
|
4
|
+
* and accept them only when they belong to this repo (#1520, navigator#190).
|
|
5
|
+
*
|
|
6
|
+
* Thin CLI over `readAuflagen()` in `scripts/lib/fleet-protocol.mjs`, called by
|
|
7
|
+
* session-start Phase 7.6 after the check-in.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/fleet-auflagen.mjs <session_id>
|
|
11
|
+
*
|
|
12
|
+
* The own repo is `CLAUDE_PROJECT_DIR` (trimmed) or the working directory.
|
|
13
|
+
*
|
|
14
|
+
* stdout: one JSON line — `{ state: 'absent' }`, `{ state: 'accepted', auflagen }`
|
|
15
|
+
* or `{ state: 'rejected', reason }`. A rejected file counts as absent: the
|
|
16
|
+
* Standard-Auflagen apply. On rejection a WARN line goes to stderr.
|
|
17
|
+
*
|
|
18
|
+
* Exit codes:
|
|
19
|
+
* 0 — any of the three states (rejection is a result, not an error)
|
|
20
|
+
* 2 — usage error (no session id)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { readAuflagen } from './lib/fleet-protocol.mjs';
|
|
24
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
25
|
+
|
|
26
|
+
const PREFIX = 'fleet-auflagen:';
|
|
27
|
+
|
|
28
|
+
/** @returns {Promise<number>} exit code */
|
|
29
|
+
async function main() {
|
|
30
|
+
const sessionId = process.argv[2];
|
|
31
|
+
if (!sessionId) {
|
|
32
|
+
process.stderr.write(`${PREFIX} usage: fleet-auflagen.mjs <session_id>\n`);
|
|
33
|
+
return 2;
|
|
34
|
+
}
|
|
35
|
+
const repoRoot = (process.env.CLAUDE_PROJECT_DIR || '').trim() || process.cwd();
|
|
36
|
+
const result = await readAuflagen(sessionId, { repoRoot });
|
|
37
|
+
if (result.state === 'rejected') {
|
|
38
|
+
process.stderr.write(
|
|
39
|
+
`${PREFIX} WARN: auflagen file ignored (${result.reason}) — treated as absent, Standard-Auflagen apply\n`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
process.stdout.write(JSON.stringify(result) + '\n');
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (isMainModule(import.meta.url)) {
|
|
47
|
+
main().then(
|
|
48
|
+
(code) => { process.exitCode = code; },
|
|
49
|
+
(err) => {
|
|
50
|
+
process.stderr.write(`${PREFIX} ${err instanceof Error ? err.message : String(err)}\n`);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -54,7 +54,8 @@ import path from 'node:path';
|
|
|
54
54
|
import { fileURLToPath } from 'node:url';
|
|
55
55
|
import { execFileSync } from 'node:child_process';
|
|
56
56
|
import yaml from 'js-yaml';
|
|
57
|
-
import {
|
|
57
|
+
import { isUserInvocableSkill } from './lib/user-invocable-skills.mjs';
|
|
58
|
+
import { listCommandFiles, listSkillDirs } from './lib/plugin-surface.mjs';
|
|
58
59
|
|
|
59
60
|
const POLICY_MARKER = '# Generated by scripts/generate-agents-skills.mjs; do not edit.\n';
|
|
60
61
|
|
|
@@ -185,7 +186,9 @@ export function renderMirror(skillName, portableFm, canonical = `skills/${skillN
|
|
|
185
186
|
'',
|
|
186
187
|
'Read the linked document in full and follow its complete workflow, including prechecks and stop conditions. Resolve its link relative to this SKILL.md, not the project working directory. The plugin root is three directories above this file. Resolve package paths such as `skills/` and `scripts/` from that root and relative links in the canonical document from its own directory. Keep the user’s project as the target of project operations.',
|
|
187
188
|
];
|
|
188
|
-
|
|
189
|
+
// The source `user-invocable` travels as a string in `metadata`; absent there
|
|
190
|
+
// means absent in the source, which Claude Code reads as invocable (#1515).
|
|
191
|
+
if (command || isUserInvocableSkill(portableFm.metadata ?? {})) lines.push(
|
|
189
192
|
'',
|
|
190
193
|
'`$ARGUMENTS` means the trailing user input after the selected command skill, or an empty string when absent. Preserve flags, quoted text, and Unicode as data. Do not perform global substitution in the command document, shell expansion on the argument string, or execution of that string as shell code. Pass command arguments through structured tool parameters or safely quoted individual arguments.',
|
|
191
194
|
);
|
|
@@ -211,24 +214,14 @@ function readFrontmatter(absPath) {
|
|
|
211
214
|
|
|
212
215
|
/** List `skills/<name>` directories that carry a SKILL.md, sorted. */
|
|
213
216
|
export function listSourceSkills(pluginRoot) {
|
|
214
|
-
|
|
215
|
-
if (!existsSync(dir) || !statSync(dir).isDirectory()) return [];
|
|
216
|
-
return readdirSync(dir, { withFileTypes: true })
|
|
217
|
-
.filter((e) => e.isDirectory() && !e.name.startsWith('.') && !e.name.startsWith('_'))
|
|
218
|
-
.map((e) => e.name)
|
|
219
|
-
.filter((n) => existsSync(path.join(dir, n, 'SKILL.md')))
|
|
220
|
-
.sort();
|
|
217
|
+
return listSkillDirs(pluginRoot);
|
|
221
218
|
}
|
|
222
219
|
|
|
223
220
|
/** Union of canonical skills and commands, preserving command precedence. */
|
|
224
221
|
function listSourceEntries(pluginRoot) {
|
|
225
222
|
const sources = new Map(listSourceSkills(pluginRoot).map((name) => [name, `skills/${name}/SKILL.md`]));
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
for (const entry of readdirSync(commands, { withFileTypes: true })) {
|
|
229
|
-
if (!entry.isFile() || !entry.name.endsWith('.md') || /^[._]/.test(entry.name)) continue;
|
|
230
|
-
sources.set(entry.name.slice(0, -3), `commands/${entry.name}`);
|
|
231
|
-
}
|
|
223
|
+
for (const file of listCommandFiles(pluginRoot)) {
|
|
224
|
+
sources.set(file.slice(0, -3), `commands/${file}`);
|
|
232
225
|
}
|
|
233
226
|
return sources;
|
|
234
227
|
}
|
|
@@ -250,7 +243,7 @@ function invocationPolicy(pluginRoot, name, canonical, fm) {
|
|
|
250
243
|
return native.policy.allow_implicit_invocation;
|
|
251
244
|
}
|
|
252
245
|
}
|
|
253
|
-
if (!
|
|
246
|
+
if (!isUserInvocableSkill(fm, canonical)) return undefined;
|
|
254
247
|
}
|
|
255
248
|
if (Object.hasOwn(fm, 'disable-model-invocation') && typeof fm['disable-model-invocation'] !== 'boolean') {
|
|
256
249
|
throw new Error(`${canonical}: disable-model-invocation must be a boolean`);
|