session-orchestrator 3.24.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/architecture/SKILL.md +18 -0
- package/.agents/skills/autopilot/SKILL.md +17 -0
- package/.agents/skills/bootstrap/SKILL.md +20 -0
- package/.agents/skills/brainstorm/SKILL.md +22 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
- package/.agents/skills/debug/SKILL.md +22 -0
- package/.agents/skills/discovery/SKILL.md +20 -0
- package/.agents/skills/dispatcher/SKILL.md +15 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
- package/.agents/skills/ecosystem-health/SKILL.md +20 -0
- package/.agents/skills/eli5/SKILL.md +20 -0
- package/.agents/skills/eval/SKILL.md +21 -0
- package/.agents/skills/evolve/SKILL.md +21 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
- package/.agents/skills/gitlab-ops/SKILL.md +20 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
- package/.agents/skills/grill/SKILL.md +22 -0
- package/.agents/skills/hook-development/SKILL.md +15 -0
- package/.agents/skills/mcp-builder/SKILL.md +15 -0
- package/.agents/skills/memory-cleanup/SKILL.md +21 -0
- package/.agents/skills/mode-selector/SKILL.md +17 -0
- package/.agents/skills/npm-publish/SKILL.md +16 -0
- package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
- package/.agents/skills/persona-panel/SKILL.md +17 -0
- package/.agents/skills/plan/SKILL.md +20 -0
- package/.agents/skills/playwright-driver/SKILL.md +20 -0
- package/.agents/skills/quality-gates/SKILL.md +20 -0
- package/.agents/skills/reconcile/SKILL.md +21 -0
- package/.agents/skills/remote-offload/SKILL.md +20 -0
- package/.agents/skills/repo-audit/SKILL.md +16 -0
- package/.agents/skills/session-end/SKILL.md +20 -0
- package/.agents/skills/session-plan/SKILL.md +20 -0
- package/.agents/skills/session-start/SKILL.md +20 -0
- package/.agents/skills/spinout/SKILL.md +16 -0
- package/.agents/skills/sunset-review/SKILL.md +16 -0
- package/.agents/skills/test-runner/SKILL.md +20 -0
- package/.agents/skills/tmux-layout/SKILL.md +21 -0
- package/.agents/skills/using-orchestrator/SKILL.md +17 -0
- package/.agents/skills/vault-mirror/SKILL.md +15 -0
- package/.agents/skills/vault-sync/SKILL.md +15 -0
- package/.agents/skills/wave-executor/SKILL.md +20 -0
- package/.agents/skills/write-executable-plan/SKILL.md +22 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot.md +2 -2
- package/.cursor/commands/bootstrap.md +1 -1
- package/.cursor/commands/brainstorm.md +1 -1
- package/.cursor/commands/debug.md +1 -1
- package/.cursor/commands/discovery.md +1 -1
- package/.cursor/commands/dispatcher.md +2 -2
- package/.cursor/commands/eli5.md +2 -2
- package/.cursor/commands/eval.md +2 -2
- package/.cursor/commands/evolve.md +1 -1
- package/.cursor/commands/go.md +1 -1
- package/.cursor/commands/grill.md +2 -2
- package/.cursor/commands/memory-cleanup.md +2 -2
- package/.cursor/commands/persona-panel.md +1 -1
- package/.cursor/commands/plan.md +1 -1
- package/.cursor/commands/portfolio.md +1 -1
- package/.cursor/commands/reconcile.md +2 -2
- package/.cursor/commands/release.md +2 -2
- package/.cursor/commands/session.md +2 -2
- package/.cursor/commands/spinout.md +2 -2
- package/.cursor/commands/sunset-review.md +2 -2
- package/.cursor/commands/templates-ack.md +2 -2
- package/.cursor/commands/test.md +2 -2
- package/.cursor/skills/brainstorm/SKILL.md +1 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/quality-gates/SKILL.md +1 -1
- package/.cursor/skills/remote-offload/SKILL.md +1 -1
- package/.orchestrator/policy/blocked-commands.json +121 -0
- package/.orchestrator/policy/ecosystem.schema.json +66 -0
- package/.orchestrator/policy/quality-gates.example.json +16 -0
- package/.orchestrator/policy/quality-gates.schema.json +38 -0
- package/.orchestrator/policy/templates-policy.json +27 -0
- package/.orchestrator/policy/test-profiles.json +47 -0
- package/AGENTS.md +225 -0
- package/CHANGELOG.md +1125 -2
- package/NOTICE +11 -6
- package/README.md +127 -94
- package/agents/eval-judge.md +1 -1
- package/agents/skill-applied-judge.md +1 -1
- package/assets/wave-lifecycle.svg +98 -0
- package/commands/release.md +6 -3
- package/commands/session.md +18 -3
- package/docs/README.md +4 -0
- package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
- package/docs/baseline.md +67 -0
- package/docs/ci-setup.md +108 -62
- package/docs/codex-setup.md +65 -21
- package/docs/components.md +36 -15
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- package/docs/instruction-delivery.md +62 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +341 -0
- package/docs/pi-setup.md +6 -1
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +85 -19
- package/docs/scope-collision-guard.md +5 -5
- package/docs/session-config-reference.md +57 -56
- package/docs/session-config-template.md +6 -29
- package/docs/telemetry.md +157 -3
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/hook-import-set.json +1487 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +2 -2
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +3 -3
- package/hooks/on-session-end.mjs +2 -2
- package/hooks/on-session-start.mjs +103 -2
- package/hooks/on-stop.mjs +36 -11
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +85 -0
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +187 -431
- package/hooks/post-tool-batch-wave-signal.mjs +118 -4
- package/hooks/post-tool-failure-corrective-context.mjs +2 -2
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +13 -4
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/plugin.json +27 -0
- package/scripts/backfill-abandoned-sessions.mjs +50 -4
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +249 -0
- package/scripts/lib/agent-status.mjs +13 -2
- package/scripts/lib/auto-dream.mjs +38 -36
- package/scripts/lib/autonomy/suitability.mjs +6 -0
- package/scripts/lib/autopilot/loop.mjs +2 -2
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +82 -6
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +8 -0
- package/scripts/lib/config/block-preprocess.mjs +177 -0
- package/scripts/lib/config/broken-window.mjs +2 -1
- package/scripts/lib/config/cold-start.mjs +2 -1
- package/scripts/lib/config/config-protection.mjs +22 -2
- package/scripts/lib/config/context-coverage.mjs +2 -1
- package/scripts/lib/config/cross-repo.mjs +2 -1
- package/scripts/lib/config/custom-phases.mjs +2 -1
- package/scripts/lib/config/dialectic.mjs +2 -1
- package/scripts/lib/config/discovery-validator.mjs +2 -1
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
- package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
- package/scripts/lib/config/docs-orchestrator.mjs +2 -1
- package/scripts/lib/config/docs-staleness.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +2 -1
- package/scripts/lib/config/eval.mjs +2 -1
- package/scripts/lib/config/events-rotation.mjs +2 -1
- package/scripts/lib/config/evolve.mjs +8 -2
- package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
- package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
- package/scripts/lib/config/handover-gate.mjs +2 -1
- package/scripts/lib/config/health-endpoints.mjs +7 -2
- package/scripts/lib/config/issue-budget.mjs +2 -1
- package/scripts/lib/config/loop-guard.mjs +2 -1
- package/scripts/lib/config/memory.mjs +2 -1
- package/scripts/lib/config/moc-staleness.mjs +2 -1
- package/scripts/lib/config/persona-gate-wave.mjs +2 -1
- package/scripts/lib/config/private-config-dir.mjs +67 -0
- package/scripts/lib/config/reconcile.mjs +2 -1
- package/scripts/lib/config/remote-hosts.mjs +2 -1
- package/scripts/lib/config/section-extractor.mjs +7 -1
- package/scripts/lib/config/skill-evolution.mjs +2 -1
- package/scripts/lib/config/slopcheck.mjs +2 -1
- package/scripts/lib/config/state-md-lock.mjs +2 -1
- package/scripts/lib/config/templates-first.mjs +2 -1
- package/scripts/lib/config/test.mjs +2 -1
- package/scripts/lib/config/vault-integration.mjs +7 -1
- package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
- package/scripts/lib/config/vault-staleness.mjs +2 -1
- package/scripts/lib/config/vault-sync.mjs +2 -1
- package/scripts/lib/config/verification-auto-fix.mjs +2 -1
- package/scripts/lib/config/wave-reviewers.mjs +2 -1
- package/scripts/lib/config/worktree-orphans.mjs +2 -1
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/rank.mjs +124 -48
- package/scripts/lib/ecosystem-health.mjs +16 -2
- package/scripts/lib/eval/engine.mjs +9 -1
- package/scripts/lib/eval/session-resolve.mjs +23 -4
- package/scripts/lib/events.mjs +22 -6
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +26 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
- package/scripts/lib/host-identity.mjs +50 -11
- package/scripts/lib/instruction-budget-guard.mjs +171 -5
- package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
- package/scripts/lib/learnings/io.mjs +60 -6
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +43 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +207 -14
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +406 -0
- package/scripts/lib/project-hygiene.mjs +38 -2
- package/scripts/lib/qg-command-drift-banner.mjs +50 -12
- package/scripts/lib/quality-gate.mjs +133 -44
- package/scripts/lib/reconcile/emitter.mjs +68 -6
- package/scripts/lib/reconcile/engine.mjs +13 -4
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/session-close-backfill.mjs +67 -9
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +125 -10
- package/scripts/lib/session-lock-shape.mjs +43 -0
- package/scripts/lib/session-lock.mjs +5 -10
- package/scripts/lib/session-registry.mjs +25 -9
- package/scripts/lib/session-schema/constants.mjs +36 -2
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +18 -1
- package/scripts/lib/sessions-staleness-banner.mjs +18 -11
- package/scripts/lib/skill-health/join.mjs +17 -4
- package/scripts/lib/state-md.mjs +78 -0
- package/scripts/lib/sunset/walker.mjs +6 -0
- package/scripts/lib/telemetry/schema.mjs +181 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
- package/scripts/lib/validate/check-skill-links.mjs +163 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
- package/scripts/lib/validate/check-unwired-features.mjs +0 -2
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/vault-backfill/template.mjs +63 -6
- package/scripts/lib/vault-mirror/process.mjs +165 -42
- package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
- package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
- package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
- package/scripts/lib/wave-resource-gate.mjs +8 -2
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +118 -4
- package/scripts/materialize-wave-scope.mjs +12 -5
- package/scripts/memory-propose.mjs +19 -5
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/release.mjs +337 -29
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/sweep-expired-learnings.mjs +90 -0
- package/scripts/sync-vault-schema.mjs +3 -1
- package/scripts/telemetry.mjs +2 -2
- package/scripts/validate-plugin.mjs +161 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-preamble.md +1 -0
- package/skills/_shared/platform-tools.md +1 -1
- package/skills/_shared/state-ownership.md +1 -1
- package/skills/architecture/SKILL.md +7 -5
- package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
- package/skills/autopilot/SKILL.md +4 -18
- package/skills/claude-md-drift-check/SKILL.md +5 -1
- package/skills/claude-md-drift-check/checker.mjs +62 -2
- package/skills/convergence-monitoring/SIGNALS.md +55 -0
- package/skills/discovery/probes/vault-staleness.mjs +37 -13
- package/skills/discovery/probes-arch.md +20 -18
- package/skills/dispatcher/SKILL.md +3 -2
- package/skills/evolve/SKILL.md +65 -26
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +33 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +10 -3
- package/skills/session-end/plan-verification.md +221 -155
- package/skills/session-end/references/phase-2-quality-gate.md +93 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
- package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
- package/skills/session-end/references/session-summary-template.md +62 -0
- package/skills/session-plan/SKILL.md +49 -0
- package/skills/session-start/SKILL.md +22 -904
- package/skills/session-start/phase-8-5-express-path.md +1 -1
- package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
- package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
- package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
- package/skills/vault-sync/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +15 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
- package/skills/wave-executor/references/wave-loop-review.md +570 -0
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
- package/skills/wave-executor/wave-loop.md +14 -1309
- package/templates/_shared/journey-manifest.md +10 -6
- package/.cursor/commands/autopilot-multi.md +0 -14
- package/.cursor/commands/contract-version-bump.md +0 -14
- package/.cursor/commands/journey-audit.md +0 -14
- package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
- package/.cursor/skills/daily/SKILL.md +0 -12
- package/.cursor/skills/domain-model/SKILL.md +0 -13
- package/.cursor/skills/journey-audit/SKILL.md +0 -13
- package/.cursor/skills/skill-creator/SKILL.md +0 -13
- package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
- package/commands/autopilot-multi.md +0 -74
- package/commands/contract-version-bump.md +0 -28
- package/commands/journey-audit.md +0 -43
- package/pi/prompts/autopilot-multi.md +0 -12
- package/pi/prompts/contract-version-bump.md +0 -12
- package/pi/prompts/journey-audit.md +0 -12
- package/scripts/autopilot-multi.mjs +0 -885
- package/scripts/backfill-learnings-expires.mjs +0 -196
- package/scripts/backfill-learnings.mjs +0 -203
- package/scripts/fleet-instruction-scan.mjs +0 -141
- package/scripts/lib/autopilot/dep-graph.mjs +0 -417
- package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
- package/scripts/lib/webhook-url.mjs +0 -105
- package/scripts/lifecycle-sim-v6.mjs +0 -347
- package/scripts/migrate-learnings-jsonl.mjs +0 -189
- package/scripts/migrate-subagents-jsonl.mjs +0 -196
- package/scripts/upload-social-preview.mjs +0 -316
- package/skills/_shared/model-selection.md +0 -64
- package/skills/contract-version-bump/SKILL.md +0 -219
- package/skills/daily/SKILL.md +0 -222
- package/skills/daily/generate.sh +0 -92
- package/skills/daily/templates/daily.md.tpl +0 -36
- package/skills/journey-audit/SKILL.md +0 -270
- package/skills/skill-creator/SKILL.md +0 -168
- package/skills/ubiquitous-language/SKILL.md +0 -97
- package/skills/vault-sync/package-lock.json +0 -40
- /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
- /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* defaults from the policy file or built-in defaults apply.
|
|
15
15
|
* --files <f1,f2,...> Comma-separated file list (incremental + per-file).
|
|
16
16
|
* --session-start-ref <r> Git ref for diff base (incremental, to find changed files).
|
|
17
|
+
* --ledger-root <path> Repo root the telemetry event is pinned to (and the
|
|
18
|
+
* root session attribution is read from). Only the
|
|
19
|
+
* pre-push hook passes it; see the emission block below.
|
|
17
20
|
* -h, --help Show this help and exit.
|
|
18
21
|
*
|
|
19
22
|
* Exit codes:
|
|
@@ -31,8 +34,8 @@
|
|
|
31
34
|
* scripts/lib/gates/gate-{baseline,incremental,full,per-file}.mjs
|
|
32
35
|
*/
|
|
33
36
|
|
|
34
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
35
|
-
import { join, dirname } from 'node:path';
|
|
37
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
38
|
+
import { join, dirname, resolve } from 'node:path';
|
|
36
39
|
import { fileURLToPath } from 'node:url';
|
|
37
40
|
import { spawnSync } from 'node:child_process';
|
|
38
41
|
|
|
@@ -80,7 +83,7 @@ const argv = process.argv.slice(2);
|
|
|
80
83
|
if (argv.includes('-h') || argv.includes('--help')) {
|
|
81
84
|
process.stdout.write(
|
|
82
85
|
'Usage: run-quality-gate.mjs --variant <variant> [--config <json-or-file>] ' +
|
|
83
|
-
'[--files <file1,file2,...>] [--session-start-ref <ref>]\n\n' +
|
|
86
|
+
'[--files <file1,file2,...>] [--session-start-ref <ref>] [--ledger-root <path>]\n\n' +
|
|
84
87
|
'Variants: baseline, incremental, full-gate, per-file\n\n' +
|
|
85
88
|
'Exit codes:\n' +
|
|
86
89
|
' 0 — pass (non-blocking variants always exit 0)\n' +
|
|
@@ -94,6 +97,7 @@ let variant = '';
|
|
|
94
97
|
let config = '';
|
|
95
98
|
let files = '';
|
|
96
99
|
let sessionStartRef = '';
|
|
100
|
+
let ledgerRootArg = '';
|
|
97
101
|
|
|
98
102
|
for (let i = 0; i < argv.length; i++) {
|
|
99
103
|
const arg = argv[i];
|
|
@@ -114,6 +118,10 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
114
118
|
if (i + 1 >= argv.length) die('Missing value for --session-start-ref');
|
|
115
119
|
sessionStartRef = argv[++i];
|
|
116
120
|
break;
|
|
121
|
+
case '--ledger-root':
|
|
122
|
+
if (i + 1 >= argv.length) die('Missing value for --ledger-root');
|
|
123
|
+
ledgerRootArg = argv[++i];
|
|
124
|
+
break;
|
|
117
125
|
default:
|
|
118
126
|
die(`Unknown argument: ${arg}`);
|
|
119
127
|
}
|
|
@@ -208,6 +216,76 @@ function suiteCountsFromGateStdout(stdout) {
|
|
|
208
216
|
return admitSuiteCounts(test);
|
|
209
217
|
}
|
|
210
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Lift `test.failed_files` out of a gate sub-script's JSON stdout envelope.
|
|
221
|
+
*
|
|
222
|
+
* Envelope adapter, same posture as {@link suiteCountsFromGateStdout}: it
|
|
223
|
+
* decides only whether a NAMED-FILE measurement exists, never what the names
|
|
224
|
+
* mean. `null` — never `[]` — for every non-measurement, so the caller OMITS
|
|
225
|
+
* the key instead of publishing an empty array that reads as "no file failed".
|
|
226
|
+
*
|
|
227
|
+
* Only `gate-full.mjs` publishes the key, and only when the runner printed a
|
|
228
|
+
* file-level summary; every other variant emits a bare status string.
|
|
229
|
+
*
|
|
230
|
+
* Never throws.
|
|
231
|
+
*
|
|
232
|
+
* @param {string} stdout — the gate sub-script's captured stdout.
|
|
233
|
+
* @returns {string[]|null}
|
|
234
|
+
*/
|
|
235
|
+
function failedFilesFromGateStdout(stdout) {
|
|
236
|
+
if (typeof stdout !== 'string' || !stdout.trim()) return null;
|
|
237
|
+
let parsed;
|
|
238
|
+
try {
|
|
239
|
+
parsed = JSON.parse(stdout);
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const files = parsed?.test?.failed_files;
|
|
244
|
+
if (!Array.isArray(files) || files.length === 0) return null;
|
|
245
|
+
const named = files.filter((f) => typeof f === 'string' && f.trim());
|
|
246
|
+
return named.length > 0 ? named : null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Validate and resolve the `--ledger-root` flag (see the telemetry block below).
|
|
251
|
+
*
|
|
252
|
+
* Only the pre-push hook passes this, and it hands over a path the gate then
|
|
253
|
+
* WRITES to — so a typo must not silently create an `.orchestrator/metrics/`
|
|
254
|
+
* tree somewhere arbitrary. The check is therefore two-part: the value must be
|
|
255
|
+
* an existing DIRECTORY, and it must already contain an `.orchestrator/`
|
|
256
|
+
* directory — the marker of a root this harness has already been initialised in.
|
|
257
|
+
*
|
|
258
|
+
* The alternative check (`git rev-parse --show-toplevel` with cwd = that path,
|
|
259
|
+
* compared against the value) is rejected on cost: it spawns a process on every
|
|
260
|
+
* gate run to prove a property two `statSync` calls already prove. The one case
|
|
261
|
+
* it accepts and this one rejects — a git root that has never run the
|
|
262
|
+
* orchestrator — is precisely the case with no ledger to pin to.
|
|
263
|
+
*
|
|
264
|
+
* A bad value NEVER crashes the gate: it warns once on stderr and returns
|
|
265
|
+
* `null`, which restores the previous resolution
|
|
266
|
+
* (`CLAUDE_PROJECT_DIR ?? CODEX_PROJECT_DIR ?? repoRoot`) at the call sites.
|
|
267
|
+
* The gate's exit code is the authoritative output; telemetry is best-effort.
|
|
268
|
+
*
|
|
269
|
+
* @param {string} value — raw flag value (`''` when the flag was not passed).
|
|
270
|
+
* @returns {string|null} absolute, validated root — or `null` to fall back.
|
|
271
|
+
*/
|
|
272
|
+
function resolveLedgerRoot(value) {
|
|
273
|
+
const raw = (value || '').trim();
|
|
274
|
+
if (!raw) return null;
|
|
275
|
+
const abs = resolve(raw);
|
|
276
|
+
try {
|
|
277
|
+
if (statSync(abs).isDirectory() && statSync(join(abs, '.orchestrator')).isDirectory()) {
|
|
278
|
+
return abs;
|
|
279
|
+
}
|
|
280
|
+
} catch { /* falls through to the warn below */ }
|
|
281
|
+
warn(
|
|
282
|
+
`--ledger-root '${raw}' is not an initialised project root ` +
|
|
283
|
+
'(existing directory containing .orchestrator/) — falling back to the default ' +
|
|
284
|
+
'telemetry destination.',
|
|
285
|
+
);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
211
289
|
/**
|
|
212
290
|
* Resolve the active wave number from the wave-scope sidecar (#966 step 1).
|
|
213
291
|
*
|
|
@@ -325,24 +403,63 @@ if (result.error && typeof result.status !== 'number') {
|
|
|
325
403
|
// (single emission path). `sessionAttribution` is the shared helper in
|
|
326
404
|
// events.mjs (#941); this CLI wrapper runs against the CWD `repoRoot`, so the
|
|
327
405
|
// bare emitEvent destination (SO_PROJECT_DIR default) is correct here.
|
|
406
|
+
//
|
|
407
|
+
// EXCEPT under the pre-push hook, which is the one caller that runs the gate in
|
|
408
|
+
// a tree that is about to be DELETED. `.husky/pre-push` materialises the tracked
|
|
409
|
+
// tree into a temp dir and deliberately scrubs every `*PROJECT_DIR` name before
|
|
410
|
+
// invoking the gate there, so `getProjectDir()` resolves to that temp tree (it
|
|
411
|
+
// carries both a CLAUDE.md (or AGENTS.md) and a .git) and the record lands in
|
|
412
|
+
// `<tmp>/.orchestrator/metrics/events.jsonl`, which the hook's EXIT trap then
|
|
413
|
+
// removes. Measured 2026-09-06: a pre-push run that BLOCKED a push left no
|
|
414
|
+
// `orchestrator.quality_gate.failed` line in this repo's ledger at all — the
|
|
415
|
+
// gate failure was, by construction, the one event that could never be recorded.
|
|
416
|
+
//
|
|
417
|
+
// `--ledger-root` is that hook's channel for handing back the root it already
|
|
418
|
+
// knows (`git rev-parse --show-toplevel`, read BEFORE it cds). It pins ONLY the
|
|
419
|
+
// telemetry destination and the attribution root — every other path the gate
|
|
420
|
+
// resolves stays inside the tree actually under test, which is the whole point
|
|
421
|
+
// of the materialisation. Absent (every other caller) → unchanged behaviour:
|
|
422
|
+
// `emitEvent`'s own default resolution.
|
|
423
|
+
//
|
|
424
|
+
// It is an ARGV FLAG and not an env var, and that is load-bearing. Measured
|
|
425
|
+
// 2026-09-06 with the env-var form: `SO_GATE_LEDGER_ROOT=$tmp npx vitest run
|
|
426
|
+
// tests/scripts/run-quality-gate.test.mjs -t "telemetry emission"` → `8 failed |
|
|
427
|
+
// 1 passed`. The chain was: hook exports the var → `npm run quality-gate` →
|
|
428
|
+
// `gate-full.mjs` spawns `npm test` → every vitest worker inherits it → the
|
|
429
|
+
// suite's own gate spawns spread `...process.env`, so the pinned root outranked
|
|
430
|
+
// their per-test project dir and the gate's telemetry tests wrote to the hook's
|
|
431
|
+
// root. The gate that releases 4.0.0 would have blocked on itself. An env var is
|
|
432
|
+
// inherited by every descendant; a flag reaches exactly one process.
|
|
433
|
+
//
|
|
328
434
|
// Best-effort: a telemetry failure must NEVER alter the gate's authoritative
|
|
329
435
|
// exit code — which is why the counts parse also lives inside this try.
|
|
330
436
|
const exitCode = result.status ?? 1;
|
|
437
|
+
const ledgerRoot = resolveLedgerRoot(ledgerRootArg);
|
|
331
438
|
try {
|
|
332
439
|
const counts = suiteCountsFromGateStdout(gateStdout);
|
|
440
|
+
// The names behind `counts.failed`. Absent, never `[]` — see
|
|
441
|
+
// `failedFilesFromGateStdout`.
|
|
442
|
+
const failedFiles = failedFilesFromGateStdout(gateStdout);
|
|
333
443
|
// Wave-scope sidecar is read from the SAME project dir the event lands in
|
|
334
444
|
// (emitEvent's own destination precedence), so a tmp-scoped run cannot pick
|
|
335
445
|
// up the host repo's live wave. Mirrors the hook's projectDir resolution.
|
|
336
446
|
const waveNumber = resolveWaveNumber(
|
|
337
|
-
process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? repoRoot,
|
|
447
|
+
ledgerRoot ?? process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? repoRoot,
|
|
448
|
+
);
|
|
449
|
+
await emitEvent(
|
|
450
|
+
`orchestrator.quality_gate.${exitCode === 0 ? 'passed' : 'failed'}`,
|
|
451
|
+
{
|
|
452
|
+
variant,
|
|
453
|
+
exit_code: exitCode,
|
|
454
|
+
...(counts ? { counts } : {}),
|
|
455
|
+
...(failedFiles ? { failed_files: failedFiles } : {}),
|
|
456
|
+
...(waveNumber !== null ? { wave_number: waveNumber } : {}),
|
|
457
|
+
...sessionAttribution(ledgerRoot ?? repoRoot),
|
|
458
|
+
},
|
|
459
|
+
// `{}` is byte-identical to omitting the argument (`opts.repoRoot ??
|
|
460
|
+
// getProjectDir()`), so the default path is untouched.
|
|
461
|
+
ledgerRoot ? { repoRoot: ledgerRoot } : {},
|
|
338
462
|
);
|
|
339
|
-
await emitEvent(`orchestrator.quality_gate.${exitCode === 0 ? 'passed' : 'failed'}`, {
|
|
340
|
-
variant,
|
|
341
|
-
exit_code: exitCode,
|
|
342
|
-
...(counts ? { counts } : {}),
|
|
343
|
-
...(waveNumber !== null ? { wave_number: waveNumber } : {}),
|
|
344
|
-
...sessionAttribution(repoRoot),
|
|
345
|
-
});
|
|
346
463
|
} catch { /* best-effort telemetry — gate result is authoritative */ }
|
|
347
464
|
|
|
348
465
|
process.exit(exitCode);
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* Usage:
|
|
24
24
|
* node scripts/sweep-expired-learnings.mjs [--prune] [--dry-run|--apply] [--json]
|
|
25
25
|
* [--grace-days N] [--entries PATH] [--file PATH] [--archive PATH]
|
|
26
|
+
* [--appended N] [--boosted M] [--duration-ms D] [--skipped a,b] [--repo-root PATH]
|
|
26
27
|
*
|
|
27
28
|
* Flags:
|
|
28
29
|
* --prune Decision-driven prune+consolidate+rewrite instead of the
|
|
@@ -40,6 +41,23 @@
|
|
|
40
41
|
* store.
|
|
41
42
|
* --file PATH Learnings store (default: .orchestrator/metrics/learnings.jsonl)
|
|
42
43
|
* --archive PATH Archive sidecar (default: .orchestrator/metrics/learnings-archive.jsonl)
|
|
44
|
+
* --appended N PRUNE ONLY (#1206). New learnings written this `/evolve`
|
|
45
|
+
* run (Step 3.5(4)); folded into the mechanical
|
|
46
|
+
* `orchestrator.evolve.completed` emit alongside this
|
|
47
|
+
* call's own `pruned` count. Default 0.
|
|
48
|
+
* --boosted M PRUNE ONLY (#1206). Existing learnings reinforced this
|
|
49
|
+
* run (Step 3.5(2)). Default 0.
|
|
50
|
+
* --duration-ms D PRUNE ONLY (#1206). Elapsed ms since the run's telemetry
|
|
51
|
+
* start marker. Default 0.
|
|
52
|
+
* --skipped a,b PRUNE ONLY (#1206). Comma-separated list of optional
|
|
53
|
+
* steps that ran but were themselves skipped this run
|
|
54
|
+
* (HR-105 — e.g. `skill-evolution-off`). Default none.
|
|
55
|
+
* --repo-root PATH PRUNE ONLY (#1206). Repo root the
|
|
56
|
+
* `orchestrator.evolve.completed` record is pinned to.
|
|
57
|
+
* No default and NO process.cwd() fallback (#1119) — when
|
|
58
|
+
* omitted, no event is emitted at all (stderr WARN, exit
|
|
59
|
+
* code unaffected). Emission is best-effort and never
|
|
60
|
+
* changes this command's exit code or stdout contract.
|
|
43
61
|
*
|
|
44
62
|
* Exit codes:
|
|
45
63
|
* 0 Success (including no-op when nothing is archive-eligible)
|
|
@@ -51,6 +69,7 @@
|
|
|
51
69
|
import { existsSync } from 'node:fs';
|
|
52
70
|
import { sweepExpiredLearnings, pruneLearnings } from './lib/learnings/expiry-sweep.mjs';
|
|
53
71
|
import { readLearnings } from './lib/learnings/io.mjs';
|
|
72
|
+
import { emitEvolveCompleted } from './lib/learnings/evolve-telemetry.mjs';
|
|
54
73
|
|
|
55
74
|
const DEFAULT_FILE = '.orchestrator/metrics/learnings.jsonl';
|
|
56
75
|
const DEFAULT_ARCHIVE = '.orchestrator/metrics/learnings-archive.jsonl';
|
|
@@ -97,6 +116,20 @@ function parseArgs(argv) {
|
|
|
97
116
|
entries: null,
|
|
98
117
|
file: DEFAULT_FILE,
|
|
99
118
|
archive: DEFAULT_ARCHIVE,
|
|
119
|
+
appended: 0,
|
|
120
|
+
boosted: 0,
|
|
121
|
+
durationMs: 0,
|
|
122
|
+
skipped: [],
|
|
123
|
+
// #1119 — NO process.cwd() fallback. Most CLI callers (every test in this
|
|
124
|
+
// file except the SKILL.md-extraction fixture, which sets its own tmp
|
|
125
|
+
// `cwd`) invoke this script from the repo root without `--repo-root`; a
|
|
126
|
+
// cwd fallback would silently append synthetic `orchestrator.evolve.
|
|
127
|
+
// completed` records to the operator's REAL fleet ledger on every
|
|
128
|
+
// `--prune --apply` run in `npm test`. `emitEvolveCompleted()` already
|
|
129
|
+
// refuses to emit (stderr WARN, no throw) when repoRoot is absent — same
|
|
130
|
+
// fail-closed contract as `emitReconcileCompleted` / `express-path.mjs`.
|
|
131
|
+
repoRoot: null,
|
|
132
|
+
telemetryExplicit: false,
|
|
100
133
|
};
|
|
101
134
|
for (let i = 0; i < argv.length; i++) {
|
|
102
135
|
const a = argv[i];
|
|
@@ -122,6 +155,37 @@ function parseArgs(argv) {
|
|
|
122
155
|
args.file = argv[++i];
|
|
123
156
|
} else if (a === '--archive') {
|
|
124
157
|
args.archive = argv[++i];
|
|
158
|
+
} else if (a === '--appended') {
|
|
159
|
+
const raw = argv[++i];
|
|
160
|
+
const v = Number(raw);
|
|
161
|
+
if (!Number.isInteger(v) || v < 0) {
|
|
162
|
+
usageError(`--appended requires a non-negative integer, got: ${raw}`);
|
|
163
|
+
}
|
|
164
|
+
args.appended = v;
|
|
165
|
+
args.telemetryExplicit = true;
|
|
166
|
+
} else if (a === '--boosted') {
|
|
167
|
+
const raw = argv[++i];
|
|
168
|
+
const v = Number(raw);
|
|
169
|
+
if (!Number.isInteger(v) || v < 0) {
|
|
170
|
+
usageError(`--boosted requires a non-negative integer, got: ${raw}`);
|
|
171
|
+
}
|
|
172
|
+
args.boosted = v;
|
|
173
|
+
args.telemetryExplicit = true;
|
|
174
|
+
} else if (a === '--duration-ms') {
|
|
175
|
+
const raw = argv[++i];
|
|
176
|
+
const v = Number(raw);
|
|
177
|
+
if (!Number.isFinite(v) || v < 0) {
|
|
178
|
+
usageError(`--duration-ms requires a non-negative number, got: ${raw}`);
|
|
179
|
+
}
|
|
180
|
+
args.durationMs = v;
|
|
181
|
+
args.telemetryExplicit = true;
|
|
182
|
+
} else if (a === '--skipped') {
|
|
183
|
+
const raw = argv[++i];
|
|
184
|
+
args.skipped = typeof raw === 'string' ? raw.split(',').filter((s) => s.length > 0) : [];
|
|
185
|
+
args.telemetryExplicit = true;
|
|
186
|
+
} else if (a === '--repo-root') {
|
|
187
|
+
args.repoRoot = argv[++i];
|
|
188
|
+
args.telemetryExplicit = true;
|
|
125
189
|
} else if (a === '--help' || a === '-h') {
|
|
126
190
|
printHelp();
|
|
127
191
|
process.exit(0);
|
|
@@ -140,6 +204,12 @@ function parseArgs(argv) {
|
|
|
140
204
|
if (!args.prune && args.entries !== null) {
|
|
141
205
|
usageError('--entries is only valid with --prune');
|
|
142
206
|
}
|
|
207
|
+
if (!args.prune && args.telemetryExplicit) {
|
|
208
|
+
usageError(
|
|
209
|
+
'--appended/--boosted/--duration-ms/--skipped/--repo-root are only valid with --prune ' +
|
|
210
|
+
'(the sweep path never emits orchestrator.evolve.completed)',
|
|
211
|
+
);
|
|
212
|
+
}
|
|
143
213
|
return args;
|
|
144
214
|
}
|
|
145
215
|
|
|
@@ -265,6 +335,26 @@ async function runPrune(args) {
|
|
|
265
335
|
...result,
|
|
266
336
|
};
|
|
267
337
|
|
|
338
|
+
// #1206 — the ONE mechanical call site for `orchestrator.evolve.completed`:
|
|
339
|
+
// this `--prune --apply` invocation IS `/evolve analyze`'s Step 3.5(5) store
|
|
340
|
+
// write, so folding the emit in here (instead of a separate
|
|
341
|
+
// `emit-event.mjs` call in skill prose) means the event can no longer be
|
|
342
|
+
// forgotten independently of the write it reports on. Gated on `!dryRun` —
|
|
343
|
+
// a `--prune --dry-run` preview (the docs' own recommended pre-check for a
|
|
344
|
+
// hand-assembled `--entries` sidecar) never wrote anything, so it must not
|
|
345
|
+
// report a completed run either. Best-effort — never changes this
|
|
346
|
+
// command's exit code or stdout contract.
|
|
347
|
+
if (!args.dryRun) {
|
|
348
|
+
await emitEvolveCompleted({
|
|
349
|
+
repoRoot: args.repoRoot,
|
|
350
|
+
appended: args.appended,
|
|
351
|
+
boosted: args.boosted,
|
|
352
|
+
pruned: result.archived,
|
|
353
|
+
durationMs: args.durationMs,
|
|
354
|
+
skipped: args.skipped,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
268
358
|
if (args.json) {
|
|
269
359
|
process.stdout.write(JSON.stringify(summary) + '\n');
|
|
270
360
|
} else {
|
|
@@ -141,7 +141,9 @@ function stripCommentLine(line) {
|
|
|
141
141
|
return null;
|
|
142
142
|
}
|
|
143
143
|
// Single-line doc comment
|
|
144
|
-
|
|
144
|
+
// A bare `//` (blank line inside a canonical comment run) is a comment too —
|
|
145
|
+
// without this the generated block carried a stray `//` (measured 2026-09-03).
|
|
146
|
+
if (trimmed === '//' || (trimmed.startsWith('// ') && !trimmed.startsWith('// ──'))) {
|
|
145
147
|
// Keep structural separator comments, drop narrative ones
|
|
146
148
|
return null;
|
|
147
149
|
}
|
package/scripts/telemetry.mjs
CHANGED
|
@@ -33,7 +33,7 @@ import { queueStats } from './lib/telemetry/queue.mjs';
|
|
|
33
33
|
import { flush, buildBatch } from './lib/telemetry/sync.mjs';
|
|
34
34
|
import { loadOwnerConfig } from './lib/owner-yaml.mjs';
|
|
35
35
|
import { readPluginVersionFromPackageJson } from './lib/bootstrap-lock-freshness.mjs';
|
|
36
|
-
import {
|
|
36
|
+
import { getPluginRoot } from './lib/platform.mjs';
|
|
37
37
|
|
|
38
38
|
const EXIT_OK = 0;
|
|
39
39
|
const EXIT_USER = 1;
|
|
@@ -69,7 +69,7 @@ ENV KILL-SWITCHES
|
|
|
69
69
|
|
|
70
70
|
/** Plugin version for `--version` — single-sourced via readPluginVersionFromPackageJson (null → 'unknown'). */
|
|
71
71
|
function readPkgVersion() {
|
|
72
|
-
return readPluginVersionFromPackageJson(
|
|
72
|
+
return readPluginVersionFromPackageJson(getPluginRoot()) ?? 'unknown';
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
@@ -94,6 +94,42 @@ function runCheck(script) {
|
|
|
94
94
|
return result.status ?? 1;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Advisory second pass for `check-skill-script-paths.mjs` (#1208): run it a
|
|
99
|
+
* second time with `--strict-sh --json` so the flag has a run surface inside
|
|
100
|
+
* validate-plugin, without promoting `.sh` findings to blocking here — the
|
|
101
|
+
* BLOCKING `.mjs`-only call above stays exactly as it is. Same non-blocking
|
|
102
|
+
* shape as the `check-unwired-features.mjs` / `check-learning-provenance.mjs`
|
|
103
|
+
* / `check-vcs-repo-flag.mjs` calls further down (exit code deliberately
|
|
104
|
+
* ignored; `checkFailed` is never set from this call). `runCheck()` cannot be
|
|
105
|
+
* reused here because it always spawns `[script, PLUGIN_ROOT]` with no room
|
|
106
|
+
* for extra flags, so this is a small dedicated spawn instead.
|
|
107
|
+
*
|
|
108
|
+
* @returns {void}
|
|
109
|
+
*/
|
|
110
|
+
function runStrictShAdvisory() {
|
|
111
|
+
const result = spawnSync(
|
|
112
|
+
'node',
|
|
113
|
+
[path.join(VALIDATE_DIR, 'check-skill-script-paths.mjs'), PLUGIN_ROOT, '--strict-sh', '--json'],
|
|
114
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
115
|
+
);
|
|
116
|
+
let parsed;
|
|
117
|
+
try {
|
|
118
|
+
parsed = JSON.parse(result.stdout || '');
|
|
119
|
+
} catch {
|
|
120
|
+
process.stdout.write(
|
|
121
|
+
' WARN: check-skill-script-paths --strict-sh: could not parse JSON output (advisory)\n',
|
|
122
|
+
);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const shDead = (parsed.findings || []).filter(
|
|
126
|
+
(f) => f.kind === 'missing-path' && f.path.endsWith('.sh'),
|
|
127
|
+
).length;
|
|
128
|
+
process.stdout.write(
|
|
129
|
+
` check-skill-script-paths --strict-sh: ${shDead} dead .sh citation(s) (advisory)\n`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
97
133
|
/**
|
|
98
134
|
* Run the surface-count drift family (issue #663) from the claude-md-drift-check
|
|
99
135
|
* checker over the doc surfaces that carry artifact counts (README.md +
|
|
@@ -175,6 +211,105 @@ function runDriftCheck() {
|
|
|
175
211
|
return 0;
|
|
176
212
|
}
|
|
177
213
|
|
|
214
|
+
/**
|
|
215
|
+
* BLOCKING (#1224): the committed hook-reachable allowlist
|
|
216
|
+
* (`hooks/_lib/hook-import-set.json`) must match a fresh crawl of the four hook
|
|
217
|
+
* manifests. The allowlist is what gates `hooks/post-edit-import-probe.mjs`, so
|
|
218
|
+
* a stale set silently NARROWS that probe — a newly hook-reachable module would
|
|
219
|
+
* be edited without ever being checked, which is precisely the 2026-09-04
|
|
220
|
+
* host-wide-block incident class the probe exists to catch. A rotting allowlist
|
|
221
|
+
* is therefore a real defect, not a cosmetic drift, hence FAIL rather than WARN.
|
|
222
|
+
*
|
|
223
|
+
* `scripts/generate-hook-import-set.mjs --check` owns the comparison (it exits 1
|
|
224
|
+
* on drift); this adapter only translates it into validate-plugin's vocabulary.
|
|
225
|
+
* `runCheck()` cannot be reused — it spawns from `scripts/lib/validate/` and
|
|
226
|
+
* passes PLUGIN_ROOT positionally, while the generator takes `--plugin-root`.
|
|
227
|
+
*
|
|
228
|
+
* @returns {number} 0 when in sync, 1 on drift or a generator error.
|
|
229
|
+
*/
|
|
230
|
+
function runHookImportSetCheck() {
|
|
231
|
+
console.log('--- Check: hook-import-set drift (#1224) ---');
|
|
232
|
+
const script = path.join(SCRIPT_DIR, 'generate-hook-import-set.mjs');
|
|
233
|
+
const result = spawnSync(
|
|
234
|
+
process.execPath,
|
|
235
|
+
[script, '--plugin-root', PLUGIN_ROOT, '--check'],
|
|
236
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
237
|
+
);
|
|
238
|
+
const combined = ((result.stdout ?? '') + (result.stderr ?? '')).trim();
|
|
239
|
+
|
|
240
|
+
// A silent exit 0 is NOT a pass. The generator's CLI entry is gated on
|
|
241
|
+
// `import.meta.url === file://${process.argv[1]}`, which does not hold when
|
|
242
|
+
// the spawned path traverses a symlink (measured: `/tmp` → `/private/tmp` on
|
|
243
|
+
// macOS makes the whole CLI body a no-op that still exits 0). Demanding the
|
|
244
|
+
// in-sync marker turns that failure mode from a silent pass into a finding.
|
|
245
|
+
const inSync = combined.match(/(\d+) modules, in sync/);
|
|
246
|
+
if (result.status === 0 && inSync) {
|
|
247
|
+
console.log(` PASS: hook-import-set: in sync (${inSync[1]} entries)`);
|
|
248
|
+
totalPass += 1;
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
if (result.status === 0) {
|
|
252
|
+
console.log(' FAIL: hook-import-set: generator exited 0 without reporting a comparison '
|
|
253
|
+
+ `(no in-sync marker in output: ${JSON.stringify(combined.slice(0, 120))})`);
|
|
254
|
+
totalFail += 1;
|
|
255
|
+
return 1;
|
|
256
|
+
}
|
|
257
|
+
const detail = combined.split('\n').filter(Boolean).pop()
|
|
258
|
+
?? `generator exited ${result.status}`;
|
|
259
|
+
console.log(` FAIL: hook-import-set: ${detail.replace(/^✗\s*hook-import-set:\s*/, '')}`);
|
|
260
|
+
console.log(' Remedy: node scripts/generate-hook-import-set.mjs && stage hooks/_lib/hook-import-set.json');
|
|
261
|
+
totalFail += 1;
|
|
262
|
+
return 1;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* BLOCKING: the cross-harness portable surface (`AGENTS.md` +
|
|
267
|
+
* `.agents/skills/`) must match a fresh generation from `CLAUDE.md` +
|
|
268
|
+
* `skills/`. 7 of 8 surveyed harnesses read `AGENTS.md` and never `CLAUDE.md`,
|
|
269
|
+
* so a stale or absent copy means those harnesses silently see NO Session
|
|
270
|
+
* Config — a failure that is invisible from inside Claude Code, which is
|
|
271
|
+
* exactly why it needs a mechanical gate rather than a convention.
|
|
272
|
+
*
|
|
273
|
+
* `scripts/generate-agents-skills.mjs --check` owns the comparison (exit 1 on
|
|
274
|
+
* drift); this adapter only translates it. `runCheck()` cannot be reused — it
|
|
275
|
+
* spawns from `scripts/lib/validate/` and passes PLUGIN_ROOT positionally,
|
|
276
|
+
* while the generator takes `--plugin-root`. The in-sync marker is demanded
|
|
277
|
+
* for the same reason `runHookImportSetCheck()` demands one: a spawn whose
|
|
278
|
+
* path traverses a symlink can make the generator's CLI body a no-op that
|
|
279
|
+
* still exits 0.
|
|
280
|
+
*
|
|
281
|
+
* @returns {number} 0 when in sync, 1 on drift or a generator error.
|
|
282
|
+
*/
|
|
283
|
+
function runAgentsSurfaceCheck() {
|
|
284
|
+
console.log('--- Check: cross-harness portable surface (AGENTS.md + .agents/skills/) ---');
|
|
285
|
+
const script = path.join(SCRIPT_DIR, 'generate-agents-skills.mjs');
|
|
286
|
+
const result = spawnSync(
|
|
287
|
+
process.execPath,
|
|
288
|
+
[script, '--plugin-root', PLUGIN_ROOT, '--check'],
|
|
289
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
290
|
+
);
|
|
291
|
+
const combined = ((result.stdout ?? '') + (result.stderr ?? '')).trim();
|
|
292
|
+
|
|
293
|
+
const inSync = combined.match(/agents-surface: (\d+) artefact\(s\), in sync/);
|
|
294
|
+
if (result.status === 0 && inSync) {
|
|
295
|
+
console.log(` PASS: agents-surface: in sync (${inSync[1]} artefacts)`);
|
|
296
|
+
totalPass += 1;
|
|
297
|
+
return 0;
|
|
298
|
+
}
|
|
299
|
+
if (result.status === 0) {
|
|
300
|
+
console.log(' FAIL: agents-surface: generator exited 0 without reporting a comparison '
|
|
301
|
+
+ `(no in-sync marker in output: ${JSON.stringify(combined.slice(0, 120))})`);
|
|
302
|
+
totalFail += 1;
|
|
303
|
+
return 1;
|
|
304
|
+
}
|
|
305
|
+
for (const line of combined.split('\n').filter((l) => l.startsWith('✗'))) {
|
|
306
|
+
console.log(` FAIL: ${line.replace(/^✗\s*/, '')}`);
|
|
307
|
+
totalFail += 1;
|
|
308
|
+
}
|
|
309
|
+
console.log(' Remedy: node scripts/generate-agents-skills.mjs');
|
|
310
|
+
return 1;
|
|
311
|
+
}
|
|
312
|
+
|
|
178
313
|
// ---------------------------------------------------------------------------
|
|
179
314
|
// Run all checks — same order as validate-plugin.sh
|
|
180
315
|
// plugin.json checks are prerequisite; abort early if they fail.
|
|
@@ -220,6 +355,9 @@ if (runDriftCheck() !== 0) checkFailed = 1;
|
|
|
220
355
|
process.stdout.write('\n');
|
|
221
356
|
if (runCheck('check-hooks-symmetry.mjs') !== 0) checkFailed = 1;
|
|
222
357
|
|
|
358
|
+
process.stdout.write('\n');
|
|
359
|
+
if (runHookImportSetCheck() !== 0) checkFailed = 1;
|
|
360
|
+
|
|
223
361
|
process.stdout.write('\n');
|
|
224
362
|
if (runCheck('check-guard-requires-parity.mjs') !== 0) checkFailed = 1;
|
|
225
363
|
|
|
@@ -247,6 +385,18 @@ if (runCheck('check-pi-prompts.mjs') !== 0) checkFailed = 1;
|
|
|
247
385
|
process.stdout.write('\n');
|
|
248
386
|
if (runCheck('check-cursor-adapter.mjs') !== 0) checkFailed = 1;
|
|
249
387
|
|
|
388
|
+
// BLOCKING: the vendor-neutral portable surface — root AGENTS.md ↔ CLAUDE.md
|
|
389
|
+
// alias, root plugin.json (agent-plugins.org 1.0.0) manifest-version parity
|
|
390
|
+
// against package.json, and the .agents/skills/ mirror against the
|
|
391
|
+
// agentskills.io field list. The generator's own --check runs first (below);
|
|
392
|
+
// this checker is the INDEPENDENT oracle over the same artefacts, so a
|
|
393
|
+
// generator bug cannot vouch for its own output.
|
|
394
|
+
process.stdout.write('\n');
|
|
395
|
+
if (runCheck('check-agents-skills.mjs') !== 0) checkFailed = 1;
|
|
396
|
+
|
|
397
|
+
process.stdout.write('\n');
|
|
398
|
+
if (runAgentsSurfaceCheck() !== 0) checkFailed = 1;
|
|
399
|
+
|
|
250
400
|
process.stdout.write('\n');
|
|
251
401
|
if (runCheck('check-session-plan-routing.mjs') !== 0) checkFailed = 1;
|
|
252
402
|
|
|
@@ -327,6 +477,17 @@ runCheck('check-doc-cli-commands.mjs');
|
|
|
327
477
|
// citation's own line or the line immediately above.
|
|
328
478
|
process.stdout.write('\n');
|
|
329
479
|
if (runCheck('check-skill-script-paths.mjs') !== 0) checkFailed = 1;
|
|
480
|
+
runStrictShAdvisory();
|
|
481
|
+
|
|
482
|
+
// BLOCKING (#1157 follow-up): a relative markdown link under skills/, commands/,
|
|
483
|
+
// agents/ or .claude/rules/ that does not resolve from the LINKING file's own
|
|
484
|
+
// directory. The check above judges cited SCRIPT paths; this one judges links
|
|
485
|
+
// between the instruction files themselves — the defect class a `references/`
|
|
486
|
+
// split creates, where the moved text is byte-identical and every `./sibling.md`
|
|
487
|
+
// in it now points one directory too deep. Three such links shipped broken on
|
|
488
|
+
// 2026-09-06 and no gate saw them; the worst silently detached the six
|
|
489
|
+
// session-end tail phases from their dispatcher.
|
|
490
|
+
if (runCheck('check-skill-links.mjs') !== 0) checkFailed = 1;
|
|
330
491
|
|
|
331
492
|
// WARN-only: a state-mutating `git` call in tests/ that names no target resolves
|
|
332
493
|
// its destination from the ambient cwd (or an inherited GIT_DIR) — the 2026-08-19
|
|
@@ -73,6 +73,7 @@ import path from 'node:path';
|
|
|
73
73
|
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
74
74
|
import { execFileSync } from 'node:child_process';
|
|
75
75
|
import { warn } from './lib/common.mjs';
|
|
76
|
+
import { MANIFEST_SESSION_KEYS } from './lib/session-identity/own-session.mjs';
|
|
76
77
|
import {
|
|
77
78
|
assertFileScopeSubset,
|
|
78
79
|
assertTestSiblingCoverage,
|
|
@@ -289,10 +290,13 @@ function validateOptionalSessionId(obj, key, errors) {
|
|
|
289
290
|
}
|
|
290
291
|
|
|
291
292
|
/**
|
|
292
|
-
* Validate the OPTIONAL session binding (#1123): `
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* call — see `skills/wave-executor/wave-loop.md` § Scope Manifest.
|
|
293
|
+
* Validate the OPTIONAL session binding (#1123): `session_id` (the raw session
|
|
294
|
+
* id of the session that WROTE this manifest) and its human-readable twin
|
|
295
|
+
* `semantic_session_id`. Both come from one `sessionAttribution(repoRoot)`
|
|
296
|
+
* call — see `skills/wave-executor/wave-loop.md` § Scope Manifest. The
|
|
297
|
+
* pre-#1153 spellings `session` / `semantic_session` are still ACCEPTED here
|
|
298
|
+
* (read side only, until the next minor release) — key names come from
|
|
299
|
+
* `MANIFEST_SESSION_KEYS` so the writer and every reader share one list.
|
|
296
300
|
*
|
|
297
301
|
* Deliberately NOT part of {@link validateRequired}, and that is a compatibility
|
|
298
302
|
* constraint rather than a preference: `wave-scope.json` is a shared
|
|
@@ -306,9 +310,25 @@ function validateOptionalSessionId(obj, key, errors) {
|
|
|
306
310
|
* @param {string[]} errors
|
|
307
311
|
* @param {string[]} warnings
|
|
308
312
|
*/
|
|
309
|
-
function
|
|
310
|
-
|
|
311
|
-
|
|
313
|
+
function validateSessionBinding(obj, errors, warnings) {
|
|
314
|
+
let present = false;
|
|
315
|
+
MANIFEST_SESSION_KEYS.current.forEach((key, i) => {
|
|
316
|
+
const legacyKey = MANIFEST_SESSION_KEYS.legacy[i];
|
|
317
|
+
const hasCurrent = validateOptionalSessionId(obj, key, errors);
|
|
318
|
+
const hasLegacy = validateOptionalSessionId(obj, legacyKey, errors);
|
|
319
|
+
// Both spellings of the SAME slot, disagreeing, is the one case the reader
|
|
320
|
+
// cannot resolve honestly: it silently prefers `key` and drops the other
|
|
321
|
+
// id, so a manifest that names two different sessions would classify as
|
|
322
|
+
// `own` for one of them. Name it here rather than let the preference decide.
|
|
323
|
+
if (hasCurrent && hasLegacy && obj[key] !== obj[legacyKey]) {
|
|
324
|
+
errors.push(
|
|
325
|
+
`${key} and legacy ${legacyKey} are both present with DIFFERENT values — ` +
|
|
326
|
+
'a manifest binds to exactly one session; drop the legacy key (accepted on ' +
|
|
327
|
+
'the read side only, until the next minor release, #1153)',
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (i === 0) present = hasCurrent || hasLegacy;
|
|
331
|
+
});
|
|
312
332
|
if (!present) {
|
|
313
333
|
warnings.push(
|
|
314
334
|
'no session field — manifest is not session-bound (legacy, #1123), so every session sharing this ' +
|
|
@@ -749,7 +769,7 @@ function validate(
|
|
|
749
769
|
const warnings = [];
|
|
750
770
|
|
|
751
771
|
validateRequired(obj, errors);
|
|
752
|
-
|
|
772
|
+
validateSessionBinding(obj, errors, warnings);
|
|
753
773
|
validateAllowedPaths(obj, errors, warnings);
|
|
754
774
|
validateBlockedCommands(obj, errors);
|
|
755
775
|
validateGates(obj, errors);
|