session-orchestrator 5.1.0 → 5.2.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/autopilot/SKILL.md +1 -0
- package/.agents/skills/bootstrap/SKILL.md +2 -0
- package/.agents/skills/brainstorm/SKILL.md +3 -0
- package/.agents/skills/close/SKILL.md +17 -0
- package/.agents/skills/debug/SKILL.md +2 -0
- package/.agents/skills/discovery/SKILL.md +2 -1
- package/.agents/skills/dispatcher/SKILL.md +2 -0
- package/.agents/skills/eli5/SKILL.md +2 -0
- package/.agents/skills/eval/SKILL.md +1 -0
- package/.agents/skills/evolve/SKILL.md +2 -1
- package/.agents/skills/go/SKILL.md +18 -0
- package/.agents/skills/grill/SKILL.md +2 -0
- package/.agents/skills/harness-audit/SKILL.md +16 -0
- package/.agents/skills/memory-cleanup/SKILL.md +1 -0
- package/.agents/skills/persona-panel/SKILL.md +1 -0
- package/.agents/skills/plan/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +17 -0
- package/.agents/skills/reconcile/SKILL.md +1 -0
- package/.agents/skills/release/SKILL.md +18 -0
- package/.agents/skills/repo-audit/SKILL.md +1 -0
- package/.agents/skills/spinout/SKILL.md +1 -0
- package/.agents/skills/sunset-review/SKILL.md +2 -0
- package/.agents/skills/test/SKILL.md +17 -0
- package/.agents/skills/ux-grill/SKILL.md +2 -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/autopilot/SKILL.md +5 -4
- package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
- package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
- package/.codex-plugin/skills/close/SKILL.md +3 -3
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +2 -0
- package/.codex-plugin/skills/convergence-monitoring/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/debug/SKILL.md +11 -4
- package/.codex-plugin/skills/discovery/SKILL.md +8 -4
- package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
- package/.codex-plugin/skills/eli5/SKILL.md +9 -4
- package/.codex-plugin/skills/eval/SKILL.md +9 -4
- package/.codex-plugin/skills/evolve/SKILL.md +9 -4
- package/.codex-plugin/skills/go/SKILL.md +3 -3
- package/.codex-plugin/skills/grill/SKILL.md +11 -4
- package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
- package/.codex-plugin/skills/npm-publish/SKILL.md +2 -0
- package/.codex-plugin/skills/npm-publish/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
- package/.codex-plugin/skills/plan/SKILL.md +8 -4
- package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
- package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
- package/.codex-plugin/skills/release/SKILL.md +3 -3
- package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
- package/.codex-plugin/skills/spinout/SKILL.md +4 -4
- package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
- package/.codex-plugin/skills/test/SKILL.md +3 -3
- package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
- package/.cursor/commands/autopilot.md +4 -4
- package/.cursor/commands/bootstrap.md +5 -4
- package/.cursor/commands/brainstorm.md +5 -4
- package/.cursor/commands/close.md +4 -3
- package/.cursor/commands/convergence-monitoring.md +13 -0
- package/.cursor/commands/debug.md +4 -4
- package/.cursor/commands/discovery.md +4 -4
- package/.cursor/commands/dispatcher.md +4 -4
- package/.cursor/commands/eli5.md +4 -4
- package/.cursor/commands/eval.md +4 -4
- package/.cursor/commands/evolve.md +4 -4
- package/.cursor/commands/go.md +4 -3
- package/.cursor/commands/grill.md +4 -4
- package/.cursor/commands/harness-audit.md +3 -3
- package/.cursor/commands/memory-cleanup.md +4 -4
- package/.cursor/commands/npm-publish.md +13 -0
- package/.cursor/commands/persona-panel.md +4 -4
- package/.cursor/commands/plan.md +5 -4
- package/.cursor/commands/portfolio.md +3 -3
- package/.cursor/commands/reconcile.md +4 -4
- package/.cursor/commands/release.md +4 -3
- package/.cursor/commands/repo-audit.md +4 -4
- package/.cursor/commands/spinout.md +4 -4
- package/.cursor/commands/sunset-review.md +4 -4
- package/.cursor/commands/test.md +3 -3
- package/.cursor/commands/ux-grill.md +4 -4
- package/.cursor/rules/010-session-workflow.mdc +2 -2
- package/.cursor/skills/bootstrap/SKILL.md +1 -0
- package/.cursor/skills/close/SKILL.md +13 -0
- package/.cursor/skills/debug/SKILL.md +0 -1
- package/.cursor/skills/discovery/SKILL.md +0 -1
- package/.cursor/skills/dispatcher/SKILL.md +0 -1
- package/.cursor/skills/eli5/SKILL.md +0 -1
- package/.cursor/skills/evolve/SKILL.md +0 -1
- package/.cursor/skills/go/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +0 -1
- package/.cursor/skills/harness-audit/SKILL.md +12 -0
- package/.cursor/skills/portfolio/SKILL.md +12 -0
- package/.cursor/skills/release/SKILL.md +13 -0
- package/.cursor/skills/repo-audit/SKILL.md +0 -1
- package/.cursor/skills/sunset-review/SKILL.md +0 -1
- package/.cursor/skills/test/SKILL.md +12 -0
- package/.cursor/skills/ux-grill/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +1 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +61 -0
- package/README.md +11 -9
- package/commands/session.md +10 -0
- package/docs/ci-setup.md +53 -0
- package/docs/codex-setup.md +1 -1
- package/docs/components.md +11 -6
- package/docs/events-schema.md +4 -1
- package/docs/install.md +16 -0
- package/docs/persona-panel.md +1 -1
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +83 -14
- package/docs/scope-collision-guard.md +2 -0
- package/docs/session-config-reference.md +6 -4
- package/hooks/_lib/hook-import-set.json +46 -6
- package/hooks/_lib/subagent-paths.mjs +15 -0
- package/hooks/_lib/vcs-create-matcher.mjs +217 -62
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/on-session-end.mjs +14 -2
- package/hooks/on-stop.mjs +43 -1
- package/hooks/post-bash-write-verify.mjs +3 -0
- package/hooks/pre-auq-clarity.mjs +3 -0
- package/hooks/pre-bash-issue-budget.mjs +103 -17
- package/hooks/pre-task-scope-disjoint.mjs +152 -3
- package/hooks/skill-invocation-telemetry.mjs +2 -1
- package/package.json +2 -1
- package/pi/prompts/autopilot.md +3 -3
- package/pi/prompts/bootstrap.md +3 -3
- package/pi/prompts/brainstorm.md +3 -3
- package/pi/prompts/close.md +2 -2
- package/pi/prompts/convergence-monitoring.md +11 -0
- package/pi/prompts/debug.md +3 -3
- package/pi/prompts/discovery.md +3 -3
- package/pi/prompts/dispatcher.md +3 -3
- package/pi/prompts/eli5.md +3 -3
- package/pi/prompts/eval.md +3 -3
- package/pi/prompts/evolve.md +3 -3
- package/pi/prompts/go.md +2 -2
- package/pi/prompts/grill.md +3 -3
- package/pi/prompts/harness-audit.md +2 -3
- package/pi/prompts/memory-cleanup.md +3 -3
- package/pi/prompts/npm-publish.md +11 -0
- package/pi/prompts/persona-panel.md +3 -3
- package/pi/prompts/plan.md +3 -3
- package/pi/prompts/portfolio.md +2 -2
- package/pi/prompts/reconcile.md +3 -3
- package/pi/prompts/release.md +3 -3
- package/pi/prompts/repo-audit.md +3 -4
- package/pi/prompts/session.md +1 -1
- package/pi/prompts/spinout.md +3 -3
- package/pi/prompts/sunset-review.md +3 -3
- package/pi/prompts/templates-ack.md +1 -1
- package/pi/prompts/test.md +3 -3
- package/pi/prompts/ux-grill.md +3 -3
- package/scripts/archive-closed-prds.mjs +2 -2
- package/scripts/auq-audit.mjs +2 -3
- package/scripts/backfill-abandoned-sessions.mjs +57 -3
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +2 -2
- package/scripts/check-package-manager.mjs +2 -2
- package/scripts/ci/assert-vitest-green.mjs +2 -1
- package/scripts/emit-session.mjs +2 -3
- package/scripts/export-hw-learnings.mjs +2 -1
- package/scripts/express-path.mjs +1 -1
- package/scripts/gc-stale-worktrees.mjs +2 -1
- package/scripts/generate-codex-skills.mjs +48 -4
- package/scripts/generate-cursor-adapter.mjs +173 -9
- package/scripts/generate-hook-import-set.mjs +12 -27
- package/scripts/generate-pi-prompts.mjs +183 -13
- package/scripts/github-protection-audit.mjs +2 -3
- package/scripts/lib/agent-frontmatter.mjs +23 -1
- package/scripts/lib/claude-md-budget-lint.mjs +2 -5
- package/scripts/lib/command-blocker.mjs +133 -5
- package/scripts/lib/config/drift-check.mjs +19 -0
- package/scripts/lib/convergence-monitor.mjs +2 -2
- package/scripts/lib/cursor-hook-bridge.mjs +2 -2
- package/scripts/lib/description-surface.mjs +2 -5
- package/scripts/lib/dispatcher/cli.mjs +2 -1
- package/scripts/lib/ecosystem-wizard.mjs +2 -1
- package/scripts/lib/fetch-baseline.mjs +3 -8
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
- package/scripts/lib/instruction-budget-guard.mjs +186 -46
- package/scripts/lib/is-main-module.mjs +82 -0
- package/scripts/lib/locks/index.mjs +32 -25
- package/scripts/lib/maintenance-due-banner.mjs +69 -3
- package/scripts/lib/peer-discovery.mjs +2 -5
- package/scripts/lib/playwright-driver/runner.mjs +2 -1
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +642 -0
- package/scripts/lib/rules-sync.mjs +2 -5
- package/scripts/lib/scope-echo.mjs +392 -7
- package/scripts/lib/session-close-backfill.mjs +58 -6
- package/scripts/lib/state-md.mjs +84 -3
- package/scripts/lib/sunset/walker.mjs +31 -4
- package/scripts/lib/tests-src-ratio.mjs +2 -6
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +2 -1
- package/scripts/lib/user-invocable-skills.mjs +185 -0
- package/scripts/lib/validate/check-banner-parity.mjs +2 -2
- package/scripts/lib/validate/check-cursor-adapter.mjs +2 -2
- package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
- package/scripts/lib/validate/check-entry-guard.mjs +366 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
- package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
- package/scripts/lib/validate/check-skill-links.mjs +27 -6
- package/scripts/lib/validate/check-skill-script-paths.mjs +2 -2
- package/scripts/lib/validate/check-test-git-config-target.mjs +2 -2
- package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +2 -2
- package/scripts/lib/validate/check-unwired-features.mjs +91 -7
- package/scripts/lib/validate/check-validator-registration.mjs +2 -2
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
- package/scripts/lib/validate-vendored-rules.mjs +35 -9
- package/scripts/lib/wave-transcript-tail.mjs +2 -2
- package/scripts/lock-reaper.mjs +2 -1
- package/scripts/materialize-wave-scope.mjs +87 -4
- package/scripts/migrate-sessions-jsonl.mjs +2 -1
- package/scripts/migrate-vault-paths.mjs +2 -3
- package/scripts/release.mjs +80 -35
- package/scripts/relocate-vault-corpus.mjs +2 -3
- package/scripts/repair-invalid-sessions.mjs +2 -2
- package/scripts/session-shape.mjs +2 -2
- package/scripts/site-numbers.mjs +35 -11
- package/scripts/sweep-expired-rules.mjs +216 -0
- package/scripts/validate-plugin.mjs +9 -0
- package/scripts/vault-consolidate.mjs +2 -2
- package/scripts/vault-mirror.mjs +2 -3
- package/scripts/wave-scope-binding.mjs +2 -3
- package/skills/_shared/bootstrap-gate.md +1 -1
- package/skills/_shared/monitor-patterns.md +1 -1
- package/skills/_shared/research-evidence.md +53 -0
- package/skills/_shared/state-ownership.md +3 -0
- package/skills/autopilot/SKILL.md +58 -4
- package/skills/bootstrap/SKILL.md +51 -1
- package/skills/brainstorm/SKILL.md +16 -0
- package/skills/claude-md-drift-check/checker.mjs +49 -11
- package/{commands/close.md → skills/close/SKILL.md} +9 -3
- package/skills/debug/SKILL.md +10 -0
- package/skills/discovery/SKILL.md +24 -1
- package/skills/discovery/probes-session.md +2 -2
- package/skills/dispatcher/SKILL.md +38 -7
- package/skills/eli5/SKILL.md +11 -0
- package/skills/eval/SKILL.md +14 -0
- package/skills/evolve/SKILL.md +8 -1
- package/skills/evolve/references/evolve-dialectic-mode.md +6 -2
- package/{commands/go.md → skills/go/SKILL.md} +9 -1
- package/skills/grill/SKILL.md +19 -0
- package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
- package/skills/hook-development/SKILL.md +46 -41
- package/skills/memory-cleanup/SKILL.md +7 -0
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +56 -1
- package/skills/persona-panel/persona-format.md +1 -1
- package/skills/plan/SKILL.md +28 -1
- package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
- package/skills/reconcile/SKILL.md +10 -0
- package/{commands/release.md → skills/release/SKILL.md} +16 -2
- package/skills/repo-audit/SKILL.md +7 -0
- package/skills/session-end/plan-verification.md +2 -2
- package/skills/session-plan/SKILL.md +1 -1
- package/skills/session-start/SKILL.md +5 -4
- package/skills/session-start/phase-8-5-express-path.md +6 -6
- package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
- package/skills/spinout/SKILL.md +12 -1
- package/skills/sunset-review/SKILL.md +13 -0
- package/{commands/test.md → skills/test/SKILL.md} +10 -4
- package/skills/ux-grill/SKILL.md +19 -1
- package/skills/wave-executor/SKILL.md +7 -4
- package/skills/wave-executor/references/wave-executor-state-init.md +13 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +3 -1
- package/skills/wave-executor/references/wave-loop-review.md +17 -1
- package/commands/autopilot.md +0 -80
- package/commands/bootstrap.md +0 -56
- package/commands/brainstorm.md +0 -48
- package/commands/debug.md +0 -36
- package/commands/discovery.md +0 -32
- package/commands/dispatcher.md +0 -59
- package/commands/eli5.md +0 -33
- package/commands/eval.md +0 -28
- package/commands/evolve.md +0 -10
- package/commands/grill.md +0 -45
- package/commands/memory-cleanup.md +0 -26
- package/commands/persona-panel.md +0 -121
- package/commands/plan.md +0 -15
- package/commands/reconcile.md +0 -23
- package/commands/repo-audit.md +0 -24
- package/commands/spinout.md +0 -15
- package/commands/sunset-review.md +0 -27
- package/commands/ux-grill.md +0 -51
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
|
|
41
41
|
import { promises as fs, existsSync } from 'node:fs';
|
|
42
42
|
import { spawnSync } from 'node:child_process';
|
|
43
|
-
import { pathToFileURL } from 'node:url';
|
|
44
43
|
import path from 'node:path';
|
|
45
44
|
import {
|
|
46
45
|
loadVaultRelocationRules,
|
|
@@ -55,6 +54,7 @@ import {
|
|
|
55
54
|
} from './lib/vault-relocation-rules.mjs';
|
|
56
55
|
import { buildBackfillIndex, parseSessionId } from './lib/vault-repo-backfill.mjs';
|
|
57
56
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
57
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
58
58
|
|
|
59
59
|
// ---------------------------------------------------------------------------
|
|
60
60
|
// Constants
|
|
@@ -1158,8 +1158,7 @@ async function main() {
|
|
|
1158
1158
|
// Entry guard (run only when invoked directly)
|
|
1159
1159
|
// ---------------------------------------------------------------------------
|
|
1160
1160
|
|
|
1161
|
-
const invokedDirectly =
|
|
1162
|
-
process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
1161
|
+
const invokedDirectly =isMainModule(import.meta.url);
|
|
1163
1162
|
|
|
1164
1163
|
if (invokedDirectly) {
|
|
1165
1164
|
main().catch((err) => {
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
import fs from 'node:fs';
|
|
38
38
|
import { parseArgs } from 'node:util';
|
|
39
39
|
import path from 'node:path';
|
|
40
|
-
import { fileURLToPath } from 'node:url';
|
|
41
40
|
|
|
42
41
|
import { repairLedger, CANONICAL_LEDGER_REL } from './lib/session-record-repair.mjs';
|
|
43
42
|
import { getProjectDir } from './lib/platform.mjs';
|
|
43
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
44
44
|
|
|
45
45
|
const USAGE =
|
|
46
46
|
'Usage: node scripts/repair-invalid-sessions.mjs [--dry-run|--apply] [--json]\n' +
|
|
@@ -200,7 +200,7 @@ async function main() {
|
|
|
200
200
|
process.exit(summary.ok === false ? 3 : 0);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
const isDirectRun =
|
|
203
|
+
const isDirectRun =isMainModule(import.meta.url);
|
|
204
204
|
if (isDirectRun) {
|
|
205
205
|
main().catch((err) => {
|
|
206
206
|
process.stderr.write(`repair-invalid-sessions: unexpected error: ${err?.stack ?? err}\n`);
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
43
43
|
import { join, resolve } from 'node:path';
|
|
44
44
|
import { parseArgs } from 'node:util';
|
|
45
|
-
import { fileURLToPath } from 'node:url';
|
|
46
45
|
import { resolveInstructionFile } from './lib/common.mjs';
|
|
47
46
|
import { parseSessionConfig } from './lib/config.mjs';
|
|
48
47
|
import { resolveAndRecordSessionShape } from './lib/session-shape.mjs';
|
|
48
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
49
49
|
|
|
50
50
|
const USAGE = [
|
|
51
51
|
'Usage: node scripts/session-shape.mjs --repo-root <path> --session-type <housekeeping|feature|deep>',
|
|
@@ -258,7 +258,7 @@ async function main() {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
// Entrypoint guard — importing this file must not run it (check-unwired-features S3).
|
|
261
|
-
if (
|
|
261
|
+
if (isMainModule(import.meta.url)) {
|
|
262
262
|
main().catch((err) => {
|
|
263
263
|
process.stderr.write(`session-shape: unexpected error: ${err?.stack ?? err}\n`);
|
|
264
264
|
process.exit(EXIT_CONFIG_IO);
|
package/scripts/site-numbers.mjs
CHANGED
|
@@ -101,10 +101,11 @@
|
|
|
101
101
|
import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
102
102
|
import { join, resolve, relative } from 'node:path';
|
|
103
103
|
import { execFileSync } from 'node:child_process';
|
|
104
|
-
import { fileURLToPath } from 'node:url';
|
|
105
104
|
|
|
106
105
|
import { writeStdoutLineSync, writeJsonAtomicSync } from './lib/io.mjs';
|
|
107
106
|
import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
|
|
107
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
108
|
+
import { slashCommandNames } from './lib/user-invocable-skills.mjs';
|
|
108
109
|
|
|
109
110
|
/** Machine-readable schema tag for the --json envelope. */
|
|
110
111
|
export const SCHEMA = 'site-numbers/1';
|
|
@@ -169,11 +170,37 @@ export function countSkills(root) {
|
|
|
169
170
|
return n;
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
/**
|
|
173
|
+
/**
|
|
174
|
+
* The product's "slash commands" number: `commands/*.md` ∪ skills carrying an
|
|
175
|
+
* EXPLICIT `user-invocable: true`, deduplicated by name.
|
|
176
|
+
*
|
|
177
|
+
* NOT `ls commands/*.md | wc -l` any more, and the change is not cosmetic. After
|
|
178
|
+
* the 2026-09-16 fold (#1370) `commands/` holds two files — `session` and
|
|
179
|
+
* `templates-ack`, the only two names a skill cannot carry — while 26 operator-
|
|
180
|
+
* facing slash commands exist. A command file only ever resolved because a
|
|
181
|
+
* same-named skill existed, and the twin additionally listed the entry twice in
|
|
182
|
+
* the `/` picker; the skill frontmatter flag is what makes a slash command a
|
|
183
|
+
* slash command now. Counting the directory would have reported 2.
|
|
184
|
+
*
|
|
185
|
+
* Dedup by name is load-bearing: a name present on BOTH sides is the picker-
|
|
186
|
+
* duplicate defect (`tests/commands/headless-bare-command-availability.test.mjs`
|
|
187
|
+
* fails on it), so it must count once here rather than inflate the tile.
|
|
188
|
+
*
|
|
189
|
+
* null (→ the tile renders as unmeasured) only when NEITHER `commands/` nor
|
|
190
|
+
* `skills/` exists — i.e. this is not a plugin tree at all. A tree with one of
|
|
191
|
+
* the two counts what it has; a missing `commands/` is now the NORMAL state, not
|
|
192
|
+
* a missing measurement.
|
|
193
|
+
*
|
|
194
|
+
* NO divergence from `skills/claude-md-drift-check/checker.mjs` `command-count`:
|
|
195
|
+
* that checker unions the same user-invocable skills (`checker.mjs:435-446`,
|
|
196
|
+
* measured 2026-09-17), and both routes reach the same verdict through the same
|
|
197
|
+
* SSOT — `isUserInvocableValue` in `scripts/lib/user-invocable-skills.mjs`. The
|
|
198
|
+
* checker stays GENERIC over consumer repos only in the sense that a repo
|
|
199
|
+
* without `skills/` sees the union collapse to the directory count on its own.
|
|
200
|
+
*/
|
|
173
201
|
export function countCommands(root) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return readdirSync(dir).filter((f) => f.endsWith('.md') && !f.startsWith('.')).length;
|
|
202
|
+
if (!isDir(join(root, 'commands')) && !isDir(join(root, 'skills'))) return null;
|
|
203
|
+
return slashCommandNames(root).length;
|
|
177
204
|
}
|
|
178
205
|
|
|
179
206
|
/**
|
|
@@ -400,7 +427,8 @@ export const METRIC_DEFS = Object.freeze([
|
|
|
400
427
|
{
|
|
401
428
|
id: 'commands',
|
|
402
429
|
provenance: false,
|
|
403
|
-
source:
|
|
430
|
+
source:
|
|
431
|
+
'ls commands/*.md | wc -l + grep -l "^user-invocable: true" skills/*/SKILL.md | wc -l (union by name — see countCommands)',
|
|
404
432
|
compute: (root) => fmtCount(countCommands(root)),
|
|
405
433
|
},
|
|
406
434
|
{
|
|
@@ -1398,11 +1426,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
1398
1426
|
return exitCode;
|
|
1399
1427
|
}
|
|
1400
1428
|
|
|
1401
|
-
const isMain =
|
|
1402
|
-
typeof process !== 'undefined' &&
|
|
1403
|
-
process.argv[1] !== null &&
|
|
1404
|
-
process.argv[1] !== undefined &&
|
|
1405
|
-
resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
|
|
1429
|
+
const isMain =isMainModule(import.meta.url);
|
|
1406
1430
|
|
|
1407
1431
|
if (isMain) {
|
|
1408
1432
|
const argv = process.argv.slice(2);
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* sweep-expired-rules.mjs — CLI for the generated-rule expiry sweep (#1377).
|
|
4
|
+
*
|
|
5
|
+
* The sibling of `scripts/sweep-expired-learnings.mjs`, one corpus over:
|
|
6
|
+
* that one moves expired LEARNINGS out of `learnings.jsonl`, this one removes
|
|
7
|
+
* expired ENTRIES from the consolidated `.claude/rules/*.md` files that were
|
|
8
|
+
* generated from them. `scripts/lib/rule-loader.mjs` already refuses to inject
|
|
9
|
+
* an expired generated rule; nothing until now removed one from disk.
|
|
10
|
+
*
|
|
11
|
+
* All planning and writing logic lives in
|
|
12
|
+
* `scripts/lib/reconcile/rule-expiry-sweep.mjs`
|
|
13
|
+
* (`planRuleExpirySweep` / `applyRuleExpirySweep`). This file is argv parsing,
|
|
14
|
+
* output formatting, the event emit, and exit codes — nothing else.
|
|
15
|
+
*
|
|
16
|
+
* SAFETY: `--dry-run` is the DEFAULT and writes nothing. `--apply` is the only
|
|
17
|
+
* write path, and it rewrites or deletes TRACKED files under `.claude/rules/`.
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* node scripts/sweep-expired-rules.mjs [--dry-run|--apply] [--json]
|
|
21
|
+
* [--grace-days N] [--now ISO] [--repo-root PATH] [--learnings PATH]
|
|
22
|
+
*
|
|
23
|
+
* Exit codes (the same contract as the learnings sweep CLI, deliberately —
|
|
24
|
+
* two sibling commands with inverted codes is a trap for the operator who
|
|
25
|
+
* learns one of them):
|
|
26
|
+
* 0 Success, including the no-op when nothing is expired
|
|
27
|
+
* 1 Usage/invocation error (bad flag or value)
|
|
28
|
+
* 2 Sweep error (an I/O or validation failure inside the lib, or any
|
|
29
|
+
* per-file error on the `--apply` path)
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { emitEvent } from './lib/events.mjs';
|
|
33
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
34
|
+
import {
|
|
35
|
+
RULE_EXPIRY_SWEEP_EVENT,
|
|
36
|
+
applyRuleExpirySweep,
|
|
37
|
+
planRuleExpirySweep,
|
|
38
|
+
} from './lib/reconcile/rule-expiry-sweep.mjs';
|
|
39
|
+
|
|
40
|
+
const DEFAULT_GRACE_DAYS = 0;
|
|
41
|
+
|
|
42
|
+
function printHelp() {
|
|
43
|
+
process.stdout.write(
|
|
44
|
+
`Usage: node scripts/sweep-expired-rules.mjs [--dry-run|--apply] [--json] [--grace-days N] [--now ISO] [--repo-root PATH] [--learnings PATH]
|
|
45
|
+
|
|
46
|
+
Removes EXPIRED entries from the consolidated machine-generated rule files in
|
|
47
|
+
.claude/rules/. An expired entry's prose block is deleted; its "## Provenance"
|
|
48
|
+
pair is KEPT as a "markers only" bullet, because /reconcile dedupes on those
|
|
49
|
+
markers and dropping one re-proposes the learning. A file is deleted only when
|
|
50
|
+
every substantive entry expired, and every provenance key is stamped terminal
|
|
51
|
+
before the unlink.
|
|
52
|
+
|
|
53
|
+
Options:
|
|
54
|
+
--dry-run Preview the plan; write nothing (DEFAULT)
|
|
55
|
+
--apply Perform the rewrites/deletes and emit the ledger event
|
|
56
|
+
--json Emit a single machine-parseable JSON summary line
|
|
57
|
+
--grace-days N Days past expiry before an entry is swept (default: ${DEFAULT_GRACE_DAYS})
|
|
58
|
+
--now ISO Injected clock (testing / what-if planning)
|
|
59
|
+
--repo-root PATH Repo root (default: process.cwd())
|
|
60
|
+
--learnings PATH Learnings store, repo-relative (default: the metrics store)
|
|
61
|
+
|
|
62
|
+
Exit codes: 0 success 1 usage/invocation error 2 sweep error
|
|
63
|
+
`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Exit 1 with a diagnostic on stderr (usage/invocation errors). */
|
|
68
|
+
function usageError(message) {
|
|
69
|
+
process.stderr.write(`sweep-expired-rules: ${message}\n`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function parseArgs(argv) {
|
|
74
|
+
const args = {
|
|
75
|
+
dryRun: true,
|
|
76
|
+
json: false,
|
|
77
|
+
graceDays: DEFAULT_GRACE_DAYS,
|
|
78
|
+
now: null,
|
|
79
|
+
repoRoot: process.cwd(),
|
|
80
|
+
learnings: null,
|
|
81
|
+
};
|
|
82
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
83
|
+
const a = argv[i];
|
|
84
|
+
if (a === '--apply') args.dryRun = false;
|
|
85
|
+
else if (a === '--dry-run') args.dryRun = true;
|
|
86
|
+
else if (a === '--json') args.json = true;
|
|
87
|
+
else if (a === '--grace-days') {
|
|
88
|
+
const raw = argv[++i];
|
|
89
|
+
const v = Number(raw);
|
|
90
|
+
if (!Number.isFinite(v) || v < 0) {
|
|
91
|
+
usageError(`--grace-days requires a non-negative number, got: ${raw}`);
|
|
92
|
+
}
|
|
93
|
+
args.graceDays = v;
|
|
94
|
+
} else if (a === '--now') {
|
|
95
|
+
const raw = argv[++i];
|
|
96
|
+
if (typeof raw !== 'string' || !Number.isFinite(Date.parse(raw))) {
|
|
97
|
+
usageError(`--now requires an ISO timestamp, got: ${raw}`);
|
|
98
|
+
}
|
|
99
|
+
args.now = raw;
|
|
100
|
+
} else if (a === '--repo-root') {
|
|
101
|
+
const raw = argv[++i];
|
|
102
|
+
if (typeof raw !== 'string' || raw.length === 0) usageError('--repo-root requires a path');
|
|
103
|
+
args.repoRoot = raw;
|
|
104
|
+
} else if (a === '--learnings') {
|
|
105
|
+
const raw = argv[++i];
|
|
106
|
+
if (typeof raw !== 'string' || raw.length === 0) usageError('--learnings requires a path');
|
|
107
|
+
args.learnings = raw;
|
|
108
|
+
} else if (a === '--help' || a === '-h') {
|
|
109
|
+
printHelp();
|
|
110
|
+
process.exit(0);
|
|
111
|
+
} else {
|
|
112
|
+
usageError(`unknown argument: ${a}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return args;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Roll the plan up into the summary both output modes render. */
|
|
119
|
+
function summarize(plan, args, applied) {
|
|
120
|
+
const counts = { rewrite: 0, delete: 0, keep: 0 };
|
|
121
|
+
for (const p of plan.plans) counts[p.action] = (counts[p.action] ?? 0) + 1;
|
|
122
|
+
return {
|
|
123
|
+
dry_run: args.dryRun,
|
|
124
|
+
grace_days: args.graceDays,
|
|
125
|
+
files_scanned: plan.plans.length,
|
|
126
|
+
rewrites: counts.rewrite,
|
|
127
|
+
deletes: counts.delete,
|
|
128
|
+
keeps: counts.keep,
|
|
129
|
+
expired_entries: plan.plans.reduce((n, p) => n + p.expiredPairIds.length, 0),
|
|
130
|
+
unresolved_pairs: plan.plans.reduce((n, p) => n + p.unresolvedPairIds.length, 0),
|
|
131
|
+
malformed_lines: plan.malformedLines,
|
|
132
|
+
rules_dir_readable: plan.ok,
|
|
133
|
+
skipped: plan.skipped,
|
|
134
|
+
plans: plan.plans.map(({ nextContent: _nextContent, ...rest }) => rest),
|
|
135
|
+
...(applied ? { applied } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function main(argv) {
|
|
140
|
+
const args = parseArgs(argv);
|
|
141
|
+
|
|
142
|
+
let plan;
|
|
143
|
+
try {
|
|
144
|
+
plan = await planRuleExpirySweep({
|
|
145
|
+
repoRoot: args.repoRoot,
|
|
146
|
+
now: args.now ?? undefined,
|
|
147
|
+
graceDays: args.graceDays,
|
|
148
|
+
learningsPath: args.learnings ?? undefined,
|
|
149
|
+
});
|
|
150
|
+
} catch (err) {
|
|
151
|
+
process.stderr.write(`sweep-expired-rules: planning failed: ${err?.message ?? err}\n`);
|
|
152
|
+
process.exit(2);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let applied = null;
|
|
156
|
+
if (!args.dryRun) {
|
|
157
|
+
applied = applyRuleExpirySweep(plan, { repoRoot: args.repoRoot, now: args.now ?? undefined });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const summary = summarize(plan, args, applied);
|
|
161
|
+
|
|
162
|
+
if (args.json) process.stdout.write(`${JSON.stringify(summary)}\n`);
|
|
163
|
+
else {
|
|
164
|
+
process.stdout.write(
|
|
165
|
+
`sweep-expired-rules: ${args.dryRun ? 'dry-run' : 'applied'} — ` +
|
|
166
|
+
`${summary.files_scanned} generated rule file(s), ${summary.rewrites} rewrite(s), ` +
|
|
167
|
+
`${summary.deletes} delete(s), ${summary.expired_entries} expired entr(ies), ` +
|
|
168
|
+
`${summary.unresolved_pairs} unresolved pair(s), ${summary.skipped.length} skipped, ` +
|
|
169
|
+
`${summary.malformed_lines} malformed learnings line(s)\n`,
|
|
170
|
+
);
|
|
171
|
+
for (const s of plan.skipped) process.stdout.write(` skipped ${s.file}: ${s.reason}\n`);
|
|
172
|
+
for (const p of plan.plans) {
|
|
173
|
+
if (p.advisory) process.stdout.write(` advisory ${p.file}: ${p.advisory}\n`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (applied) {
|
|
178
|
+
for (const e of applied.errors) {
|
|
179
|
+
process.stderr.write(`sweep-expired-rules: ${e.file}: ${e.error}\n`);
|
|
180
|
+
}
|
|
181
|
+
// Emitted on apply ONLY, and AFTER the writes: a record's presence is proof
|
|
182
|
+
// that `.claude/rules/` was actually changed, never that a preview ran.
|
|
183
|
+
// Wrapped because `emitEvent` THROWS `EventValidationError` — a telemetry
|
|
184
|
+
// failure must not turn a completed sweep into a non-zero exit.
|
|
185
|
+
try {
|
|
186
|
+
await emitEvent(
|
|
187
|
+
RULE_EXPIRY_SWEEP_EVENT,
|
|
188
|
+
{
|
|
189
|
+
rewritten: applied.rewritten.length,
|
|
190
|
+
deleted: applied.deleted.length,
|
|
191
|
+
stamped: applied.stamped,
|
|
192
|
+
write_errors: applied.errors.length,
|
|
193
|
+
expired_entries: summary.expired_entries,
|
|
194
|
+
files_scanned: summary.files_scanned,
|
|
195
|
+
source: 'sweep-expired-rules-cli',
|
|
196
|
+
},
|
|
197
|
+
{ repoRoot: args.repoRoot },
|
|
198
|
+
);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
process.stderr.write(`sweep-expired-rules: event emit failed: ${err?.message ?? err}\n`);
|
|
201
|
+
}
|
|
202
|
+
if (applied.errors.length > 0) process.exit(2);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!plan.ok) {
|
|
206
|
+
process.stderr.write(
|
|
207
|
+
'sweep-expired-rules: .claude/rules/ exists but could not be enumerated — ' +
|
|
208
|
+
'the plan above is INCOMPLETE, not clean.\n',
|
|
209
|
+
);
|
|
210
|
+
process.exit(2);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (isMainModule(import.meta.url)) {
|
|
215
|
+
await main(process.argv.slice(2));
|
|
216
|
+
}
|
|
@@ -424,6 +424,15 @@ if (runCheck('check-unicode-safety.mjs') !== 0) checkFailed = 1;
|
|
|
424
424
|
process.stdout.write('\n');
|
|
425
425
|
if (runCheck('check-dead-bridge.mjs') !== 0) checkFailed = 1;
|
|
426
426
|
|
|
427
|
+
// BLOCKING (#1371): a hand-written `process.argv[1] === import.meta.url` entry
|
|
428
|
+
// guard is false under any symlinked invocation, so main() never runs and the
|
|
429
|
+
// process still exits 0 — the caller reads silence as success. This can block
|
|
430
|
+
// honestly because the backlog was drained to zero in the same change: the check
|
|
431
|
+
// is not red on arrival for work nobody intends to do (contrast the WARN-only
|
|
432
|
+
// censuses below, each of which had a standing backlog when it landed).
|
|
433
|
+
process.stdout.write('\n');
|
|
434
|
+
if (runCheck('check-entry-guard.mjs') !== 0) checkFailed = 1;
|
|
435
|
+
|
|
427
436
|
// FAIL-capable, unlike the WARN-only censuses below: R2+R4 were measured at
|
|
428
437
|
// 1 hit / 1 TP / 0 FP each against the full test corpus, so this check cannot be
|
|
429
438
|
// red on arrival for anything but a real instance of the class. The alternatives
|
|
@@ -84,7 +84,6 @@
|
|
|
84
84
|
|
|
85
85
|
import { promises as fs } from 'node:fs';
|
|
86
86
|
import path from 'node:path';
|
|
87
|
-
import { pathToFileURL } from 'node:url';
|
|
88
87
|
|
|
89
88
|
import { die, utcTimestamp, expandTilde } from './lib/common.mjs';
|
|
90
89
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
@@ -96,6 +95,7 @@ import {
|
|
|
96
95
|
stageBackup,
|
|
97
96
|
compressAndCleanupBackup,
|
|
98
97
|
} from './lib/vault-consolidate-fs.mjs';
|
|
98
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
99
99
|
|
|
100
100
|
const DEFAULT_SOURCE = '~/Projects/vault';
|
|
101
101
|
const DEFAULT_CANONICAL = '~/Projects/Bernhard/vault';
|
|
@@ -580,7 +580,7 @@ async function main() {
|
|
|
580
580
|
// `process.argv[1]` is undefined when the module is loaded via `node -e
|
|
581
581
|
// "import(...)"` (no script path in argv), so guard against it before calling
|
|
582
582
|
// pathToFileURL — otherwise the import itself throws ERR_INVALID_ARG_TYPE.
|
|
583
|
-
if (
|
|
583
|
+
if (isMainModule(import.meta.url)) {
|
|
584
584
|
main().catch((e) => {
|
|
585
585
|
console.error(e);
|
|
586
586
|
process.exit(1);
|
package/scripts/vault-mirror.mjs
CHANGED
|
@@ -53,7 +53,6 @@ import { existsSync } from 'node:fs';
|
|
|
53
53
|
import { resolve } from 'node:path';
|
|
54
54
|
import { createInterface } from 'node:readline';
|
|
55
55
|
import { createReadStream } from 'node:fs';
|
|
56
|
-
import { pathToFileURL } from 'node:url';
|
|
57
56
|
|
|
58
57
|
import { processLearning, processSession, getMaskerStats } from './lib/vault-mirror/process.mjs';
|
|
59
58
|
import { emitMirrorEvent, emitMirrorRunEvent } from './lib/vault-mirror/telemetry.mjs';
|
|
@@ -64,6 +63,7 @@ import { resolveRepoNamespace } from './lib/vault-mirror/namespace.mjs';
|
|
|
64
63
|
import { resolveCanonicalSuffixes } from './lib/named-vault-resolver.mjs';
|
|
65
64
|
import { loadOwnerConfig } from './lib/owner-yaml.mjs';
|
|
66
65
|
import { canonicalizeSessions } from './lib/sessions-canonical.mjs';
|
|
66
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
67
67
|
|
|
68
68
|
// ── Canonical-vault helpers (#600 D2 / #607 D2) ────────────────────────────────
|
|
69
69
|
// These are module-level (above the CLI bootstrap) so the module is import-safe
|
|
@@ -166,8 +166,7 @@ export function _normalizeRemote(url) {
|
|
|
166
166
|
// (`node vault-mirror.mjs ...`). When imported from a unit test, argv belongs to
|
|
167
167
|
// the test runner — parsing it would spuriously process.exit. The exported
|
|
168
168
|
// helpers above are unaffected by this guard.
|
|
169
|
-
const _isDirectInvocation =
|
|
170
|
-
Boolean(process.argv[1]) && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
169
|
+
const _isDirectInvocation =isMainModule(import.meta.url);
|
|
171
170
|
|
|
172
171
|
if (_isDirectInvocation) {
|
|
173
172
|
let parsedFlags;
|
|
@@ -37,10 +37,9 @@
|
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
39
|
import { parseArgs } from 'node:util';
|
|
40
|
-
import { resolve as resolvePath } from 'node:path';
|
|
41
|
-
import { fileURLToPath } from 'node:url';
|
|
42
40
|
import { attributionForRecord, emitEvent } from './lib/events.mjs';
|
|
43
41
|
import { MANIFEST_SESSION_KEYS } from './lib/session-identity/own-session.mjs';
|
|
42
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
44
43
|
|
|
45
44
|
const HELP = `Usage: node scripts/wave-scope-binding.mjs [--merge] [--wave N] [--role R] [--repo-root DIR]
|
|
46
45
|
|
|
@@ -210,6 +209,6 @@ function readStdin() {
|
|
|
210
209
|
|
|
211
210
|
// `import.meta.main` is not available on every supported Node — compare argv[1]
|
|
212
211
|
// instead, so importing this module from a test never runs the CLI.
|
|
213
|
-
if (
|
|
212
|
+
if (isMainModule(import.meta.url)) {
|
|
214
213
|
await main(process.argv.slice(2));
|
|
215
214
|
}
|
|
@@ -102,7 +102,7 @@ source: projects-baseline | plugin-template | claude-init
|
|
|
102
102
|
|
|
103
103
|
A repo that has already been bootstrapped passes the gate on every subsequent invocation with zero overhead. The gate check reads three files and performs two grep operations — negligible cost.
|
|
104
104
|
|
|
105
|
-
`/bootstrap --retroactive` exists for repos that predate the gate (have `CLAUDE.md` + Session Config but no `bootstrap.lock`). It writes the lock without re-scaffolding. Documented in `
|
|
105
|
+
`/bootstrap --retroactive` exists for repos that predate the gate (have `CLAUDE.md` + Session Config but no `bootstrap.lock`). It writes the lock without re-scaffolding. Documented in `skills/bootstrap/SKILL.md`.
|
|
106
106
|
|
|
107
107
|
## Auto-Skill Dispatch (opt-in, default: off)
|
|
108
108
|
|
|
@@ -23,7 +23,7 @@ coverage check.
|
|
|
23
23
|
|
|
24
24
|
## Pattern 1 — GitLab pipeline + GitHub mirror sync (post-push)
|
|
25
25
|
|
|
26
|
-
**When.** After `git push origin main` from `
|
|
26
|
+
**When.** After `git push origin main` from `skills/close/SKILL.md` Phase 6.
|
|
27
27
|
Watches the GitLab pipeline through to a terminal status, then prints
|
|
28
28
|
the GitHub mirror's main-branch SHA so the operator can confirm parity.
|
|
29
29
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Research Evidence Contract
|
|
2
|
+
|
|
3
|
+
Use this contract when research informs a plan, audit, recommendation, or
|
|
4
|
+
research-agent handoff. Scale it to the decision: a trivial local lookup may be
|
|
5
|
+
one sentence; a comparison with several sources needs one record per material
|
|
6
|
+
claim. It does not require external research when repository evidence is enough.
|
|
7
|
+
|
|
8
|
+
## Record
|
|
9
|
+
|
|
10
|
+
For each finding that changes the recommendation, retain:
|
|
11
|
+
|
|
12
|
+
- **Source:** stable reference plus source revision or publication/update date.
|
|
13
|
+
For repository evidence, prefer `SHA:path:line`; for changing external facts,
|
|
14
|
+
include the observation date.
|
|
15
|
+
- **Basis:** label the finding `observed` for a measurement or source-code
|
|
16
|
+
inspection (name which), `documented` for a source's claim, or `inference`
|
|
17
|
+
when it follows from other evidence. A test found in source is not an
|
|
18
|
+
executed test. Never present one class as another. Counts, percentages and
|
|
19
|
+
distributions additionally require PSA-006's executed-command transcript
|
|
20
|
+
(`.claude/rules/parallel-sessions.md` § PSA-006) — command, file scope,
|
|
21
|
+
result, measurement time.
|
|
22
|
+
- **Local equivalent:** name the existing repository feature, dependency, rule,
|
|
23
|
+
or workflow that already covers the need; use `none found` only after a search
|
|
24
|
+
proportionate to the task.
|
|
25
|
+
- **Disposition:** `adopt`, `adapt`, `experiment`, `reject`, or
|
|
26
|
+
`already stronger`, with one short reason. `Adopt` still means implement
|
|
27
|
+
through the current repository's conventions; it does not authorize copying,
|
|
28
|
+
installing, publishing, or another external mutation.
|
|
29
|
+
- **Next check:** state one falsifiable next step when uncertainty remains: what
|
|
30
|
+
result would confirm or overturn the disposition.
|
|
31
|
+
|
|
32
|
+
Keep quoted text minimal. Record limitations that materially constrain the
|
|
33
|
+
claim. A source revision proves which artifact was inspected; it does not prove
|
|
34
|
+
that its documented behavior works.
|
|
35
|
+
|
|
36
|
+
## Private sources
|
|
37
|
+
|
|
38
|
+
Treat private/internal sources as evidence for an authorized private audience
|
|
39
|
+
only. Follow [Private capability context](private-capability-context.md) for
|
|
40
|
+
lookup and retention. Do not copy private identities, paths, excerpts, or
|
|
41
|
+
derived implementation details into a public plan or handoff. Re-establish a
|
|
42
|
+
public claim from an authorized public source, or omit it. Source access never
|
|
43
|
+
expands the task's implementation or external-action authority.
|
|
44
|
+
|
|
45
|
+
## Compact example
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
Source: vendor guide, rev 3.2, observed 2026-09-15
|
|
49
|
+
Basis: documented — retries use exponential backoff; runtime behavior untested
|
|
50
|
+
Local equivalent: src/retry.mjs already provides capped linear retries
|
|
51
|
+
Disposition: experiment — compare failure recovery before changing the default
|
|
52
|
+
Next check: 100 replayed failures; reject if success rate does not improve
|
|
53
|
+
```
|
|
@@ -18,6 +18,7 @@ total-waves: <N>
|
|
|
18
18
|
# Optional fields (schema-version 1, additive for backward-compat):
|
|
19
19
|
updated: <ISO 8601 UTC> # last write timestamp, touched by any writer
|
|
20
20
|
session: <session-label> # attribution/history label; normally semantic since #573, legacy UUID-v4 remains readable; never a lock/registry ownership key
|
|
21
|
+
session-id: <raw id> # the RAW/native harness session id from session.lock (#1368); omit when absent; never a lock/registry ownership key
|
|
21
22
|
session-start-ref: <sha> # git ref at session start
|
|
22
23
|
---
|
|
23
24
|
```
|
|
@@ -25,6 +26,8 @@ session-start-ref: <sha> # git ref at session start
|
|
|
25
26
|
### Required vs. optional fields
|
|
26
27
|
|
|
27
28
|
- `schema-version`, `session-type`, `branch`, `issues`, `started_at`, `status`, `current-wave`, `total-waves` — **required** in every session-owned STATE.md.
|
|
29
|
+
- `session-id` — **optional**, additive under `schema-version: 1` (#1368). The RAW/native harness session id, taken from `.orchestrator/session.lock` via `resolveSessionIds()` (`scripts/lib/state-md.mjs`). Writers OMIT the key when the lock yields none; readers MUST tolerate its absence. It exists so `/close`'s #429 pre-check can join STATE.md to sessions.jsonl on a NATIVE identity (`findRecordedSession`'s UUID fast path) instead of falling through to the semantic label. Like `session`, it grants no lock or registry ownership.
|
|
30
|
+
- `started_at` is SOURCED from the same lock (`resolveSessionStartedAt()`), never from the writer's clock — see `skills/wave-executor/references/wave-executor-state-init.md` § Pre-Wave 1b for the template and the 48-minute drift that motivated it (#1368). On the READ side `started_at` is a CORROBORATING signal, never an identity key: `findRecordedSession()` (`scripts/lib/session-close-backfill.mjs`) joins STATE.md to `sessions.jsonl` on the native `session-id` FIRST and never consults `started_at` on that path — only the legacy-LABEL path compares the two timestamps, and it tolerates up to `STARTED_AT_DRIFT_TOLERANCE_MS` = **6 h** of drift (`session-close-backfill.mjs:559`, the single definition) before vetoing a label match: wider than any plausible write-lag (the 48 minutes above), narrower than the day the label's own date component already distinguishes.
|
|
28
31
|
- `updated`, `session`, `session-start-ref` — **optional**. Added by #184. STATE.md files without these fields remain valid and should be treated as `updated: null` / `session: null`. Writers SHOULD populate these fields but readers MUST tolerate their absence. `session` is an attribution/history label, normally `<branch>-<YYYY-MM-DD>-<mode>-<n>` since #573 (Epic #568 Parallel-Aware Sessions P2.2); pre-#573 files may contain a UUID-v4 — both formats are read via `parseSessionId()` from `scripts/lib/session-id.mjs` per PRD §3 P2 row 3 (backward-compat). Neither form grants lock or registry ownership.
|
|
29
32
|
|
|
30
33
|
The `session-type: none` + `status: idle` combination is used only for bootstrap-scaffolded placeholder files (no active session).
|
|
@@ -9,12 +9,47 @@ description: >
|
|
|
9
9
|
record per loop run. Phase C scaffold (issue #277); implementation lives in
|
|
10
10
|
scripts/lib/autopilot.mjs (Phase C-1 follow-up).
|
|
11
11
|
user-invocable: true
|
|
12
|
+
argument-hint: "[--headless] [--verbose] [--max-sessions=N] [--max-hours=H] [--confidence-threshold=0.X] [--dry-run]"
|
|
12
13
|
tags: [phase-c, autopilot, autonomous, loop]
|
|
13
14
|
model: sonnet
|
|
14
15
|
---
|
|
15
16
|
|
|
16
17
|
# Autopilot Skill
|
|
17
18
|
|
|
19
|
+
## Invocation
|
|
20
|
+
|
|
21
|
+
The user invokes `/autopilot` with arguments: **$ARGUMENTS**. This is autonomous
|
|
22
|
+
session-orchestration mode — a top-level invocation only, never from inside a running
|
|
23
|
+
session.
|
|
24
|
+
|
|
25
|
+
Parse `$ARGUMENTS` before anything else. Unrecognized flags are ignored; out-of-range
|
|
26
|
+
values silently clamp to bounds. Use `parseFlags` from `scripts/lib/autopilot.mjs` for
|
|
27
|
+
canonical parsing — never re-implement clamping inline. The loop flags
|
|
28
|
+
(`--max-sessions`, `--max-hours`, `--confidence-threshold`, `--dry-run`) and their
|
|
29
|
+
defaults/bounds are tabled once in § Command Surface below. Two further flags belong to
|
|
30
|
+
the invocation surface only:
|
|
31
|
+
|
|
32
|
+
| Flag | Default | Meaning |
|
|
33
|
+
|------|---------|---------|
|
|
34
|
+
| `--headless` | `false` | Run via the standalone driver `scripts/autopilot.mjs`, which spawns `claude -p '/session <mode>'` as a child process per iteration. Required for unattended walk-away mode. Without it, `/autopilot` runs the in-process driver inside the current chat session (see § Production Wiring). |
|
|
35
|
+
| `--verbose` | `false` | Verbose driver output. |
|
|
36
|
+
|
|
37
|
+
### Headless (`claude -p`)
|
|
38
|
+
|
|
39
|
+
Headless requirements:
|
|
40
|
+
|
|
41
|
+
- Anthropic API key configured for `claude -p` (Claude Code CLI auth).
|
|
42
|
+
- `scripts/autopilot.mjs` reads `sessions.jsonl` tail after each child exit to construct
|
|
43
|
+
the `sessionRunner` return shape — the spawned session must complete cleanly and append
|
|
44
|
+
its record (session-end Phase 3.7 handles this).
|
|
45
|
+
- `AUTOPILOT_RUN_ID` env var is propagated to the child so session-end stamps it onto the
|
|
46
|
+
per-iteration `sessions.jsonl` record.
|
|
47
|
+
|
|
48
|
+
Do NOT re-implement loop logic inline — this skill and `scripts/lib/autopilot.mjs` are
|
|
49
|
+
authoritative. Kill-switches are enforced by `scripts/lib/autopilot.mjs`, not inline by
|
|
50
|
+
Claude. The runtime writes ONE record to `.orchestrator/metrics/autopilot.jsonl` per
|
|
51
|
+
invocation via atomic tmp+rename; no other code path appends to that file.
|
|
52
|
+
|
|
18
53
|
## Phase 0.5: Parallel-Aware Preamble
|
|
19
54
|
|
|
20
55
|
> Skip silently when `persistence: false` in Session Config.
|
|
@@ -177,7 +212,8 @@ Phase C-1 ships `runLoop` as a pure controller. Phase C-1.c ships `buildLiveSign
|
|
|
177
212
|
the canonical signals-assembly helper. This section documents the **in-process driver
|
|
178
213
|
protocol** (Option B from #301): how Claude — running as the coordinator in a chat
|
|
179
214
|
session — drives `runLoop` between manual `/session` invocations. The headless wrapper
|
|
180
|
-
(Option A, `scripts/autopilot.mjs` CLI spawning `claude -p`) is
|
|
215
|
+
(Option A, `scripts/autopilot.mjs` CLI spawning `claude -p`) is selected with `--headless`
|
|
216
|
+
— see § Invocation.
|
|
181
217
|
|
|
182
218
|
### Dependency-Injection Contract
|
|
183
219
|
|
|
@@ -187,8 +223,8 @@ session — drives `runLoop` between manual `/session` invocations. The headless
|
|
|
187
223
|
|---|---|---|
|
|
188
224
|
| `modeSelector` | `() => Promise<{mode, confidence, rationale?}>` | wraps `selectMode(await buildLiveSignals())` |
|
|
189
225
|
| `sessionRunner` | `({mode, autopilotRunId}) => Promise<{session_id, agent_summary?, effectiveness?}>` | wraps a `/session <mode>` invocation; reads `sessions.jsonl` tail to construct return value |
|
|
190
|
-
| `resourceEvaluator` | `() => {verdict}` |
|
|
191
|
-
| `peerCounter` | `() => number
|
|
226
|
+
| `resourceEvaluator` | `() => {verdict}` | calls `evaluate(cachedProbeSnapshot, thresholds)` from `resource-probe.mjs` over a snapshot `peerCounter` refreshed on the prior iteration — never calls `probe()` itself, which is what keeps it synchronous |
|
|
227
|
+
| `peerCounter` | `() => Promise<number>` | returns `peers.length` from `detectPeers({ sessionId, freshnessMin: 15 })` (a SESSION count, not a process count — see `host-resources.md` HR-103) while refreshing the cached `probe()` snapshot `resourceEvaluator` reads |
|
|
192
228
|
|
|
193
229
|
`abortSignal` is optional (Ctrl+C / Esc → `user-abort` kill-switch).
|
|
194
230
|
|
|
@@ -256,6 +292,25 @@ The in-process driver has Claude (the coordinator) call `/session <mode>` betwee
|
|
|
256
292
|
- **Con:** not truly autonomous — Claude must stay in the chat. Doesn't deliver
|
|
257
293
|
walk-away UX. That's Phase C-5's job.
|
|
258
294
|
|
|
295
|
+
### Headless Driver Wiring (Option A — `scripts/autopilot.mjs`)
|
|
296
|
+
|
|
297
|
+
The standalone headless driver invoked via `--headless` (see § Invocation) wires all
|
|
298
|
+
four required `runLoop` dependencies (`modeSelector`, `sessionRunner`,
|
|
299
|
+
`resourceEvaluator`, `peerCounter`) plus the optional `abortSignal` to production
|
|
300
|
+
sources — distinct from, and more concrete than, the in-process skeleton above:
|
|
301
|
+
|
|
302
|
+
- `sessionRunner` — spawns `claude -p '/session <mode>'` as a child process; after it
|
|
303
|
+
exits cleanly, reads the `sessions.jsonl` tail to construct the return shape
|
|
304
|
+
`{session_id, agent_summary?, effectiveness?}` (`scripts/autopilot.mjs` `sessionRunner`).
|
|
305
|
+
- `resourceEvaluator` — calls `evaluate(cachedProbeSnapshot, thresholds)` from
|
|
306
|
+
`scripts/lib/resource-probe.mjs`, reading a snapshot refreshed by `peerCounter` on the
|
|
307
|
+
prior iteration so the function itself stays synchronous, per the `runLoop` contract
|
|
308
|
+
(`scripts/autopilot.mjs` `resourceEvaluator`).
|
|
309
|
+
- `peerCounter` — calls `detectPeers({ sessionId: autopilotRunId, freshnessMin: 15 })` from
|
|
310
|
+
`scripts/lib/session-registry.mjs` AND refreshes the cached `probe()` snapshot in the
|
|
311
|
+
same call, returning `peers.length` (`scripts/autopilot.mjs` `makePeerCounter`).
|
|
312
|
+
- `abortSignal` — an `AbortController` aborted on the process's `SIGINT` handler.
|
|
313
|
+
|
|
259
314
|
### `autopilot_run_id` Propagation
|
|
260
315
|
|
|
261
316
|
When `runLoop` invokes `sessionRunner({mode, autopilotRunId})`, the per-iteration
|
|
@@ -387,7 +442,6 @@ Single-story `/autopilot` takes no Session Config block. Multi-story
|
|
|
387
442
|
- PRD: "Autopilot Loop" (#277; archived in the private Meta-Vault)
|
|
388
443
|
- Implementation (Phase C-1 + C-1.b): `scripts/lib/autopilot.mjs` — exports `runLoop`, `parseFlags`, `writeAutopilotJsonl`, `KILL_SWITCHES`, `FLAG_BOUNDS`, `SCHEMA_VERSION`, `DEFAULT_PEER_ABORT_THRESHOLD`, `DEFAULT_JSONL_PATH`, `DEFAULT_CARRYOVER_THRESHOLD`
|
|
389
444
|
- Tests (Phase C-1 + C-1.b): `tests/lib/autopilot.test.mjs`
|
|
390
|
-
- Command file: `commands/autopilot.md`
|
|
391
445
|
- Mode-Selector contract: `skills/mode-selector/SKILL.md`
|
|
392
446
|
- Resource probe: `scripts/lib/resource-probe.mjs`
|
|
393
447
|
- Session registry: `scripts/lib/session-registry.mjs`
|