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
|
@@ -265,9 +265,9 @@
|
|
|
265
265
|
|
|
266
266
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
267
267
|
import path from 'node:path';
|
|
268
|
-
import { pathToFileURL } from 'node:url';
|
|
269
268
|
|
|
270
269
|
import { sizingSubject } from '../learnings/sizing-subject.mjs';
|
|
270
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
271
271
|
|
|
272
272
|
/** Documented config surface — every `yaml` fence in this file is a declaration. */
|
|
273
273
|
const TEMPLATE_REL = 'docs/session-config-template.md';
|
|
@@ -652,19 +652,57 @@ export function collectExportedSymbols(body) {
|
|
|
652
652
|
return [...names];
|
|
653
653
|
}
|
|
654
654
|
|
|
655
|
+
/**
|
|
656
|
+
* Re-export targets of a barrel: the specifiers of its `export * from '…'`
|
|
657
|
+
* (and `export * as ns from '…'`) lines.
|
|
658
|
+
*
|
|
659
|
+
* `collectExportedSymbols` cannot see these — a star re-export names no symbol —
|
|
660
|
+
* so a PURE barrel reads as `exports: []`. That does not merely hide the barrel:
|
|
661
|
+
* the S4 population predicate below requires at least one export, so the barrel
|
|
662
|
+
* never enters `unreachable` at all, and is therefore unavailable as the CLUSTER
|
|
663
|
+
* ROOT of the module it re-exports. The target is then reported as its own root.
|
|
664
|
+
*
|
|
665
|
+
* Measured 2026-09-16 on the live tree: `scripts/lib/worktree.mjs` is exactly
|
|
666
|
+
* `export * from './worktree/index.mjs';`, its only importers (`workspace.mjs`,
|
|
667
|
+
* `worktree-freshness.mjs`) are themselves unreachable — yet S4 reported
|
|
668
|
+
* `scripts/lib/worktree/index.mjs`, an interior file, instead of the cluster.
|
|
669
|
+
*
|
|
670
|
+
* Only the POPULATION is affected. Edge propagation already works: the BFS walks
|
|
671
|
+
* `mentions`, and a star re-export line is not a comment, so a barrel that IS
|
|
672
|
+
* reachable already marks its target reachable (measured the same day:
|
|
673
|
+
* `worktree.mjs` carries `index.mjs` in `mentions`).
|
|
674
|
+
*
|
|
675
|
+
* Anchored at column 0 like the export-symbol grammar above, so a docblock line
|
|
676
|
+
* (`* export * from …`) and an indented string never count.
|
|
677
|
+
*
|
|
678
|
+
* @param {string} body module source
|
|
679
|
+
* @returns {string[]} re-exported module specifiers, as written
|
|
680
|
+
*/
|
|
681
|
+
export function collectReExportTargets(body) {
|
|
682
|
+
return [
|
|
683
|
+
...body.matchAll(/^export\s+\*(?:\s+as\s+[A-Za-z0-9_$]+)?\s+from\s+['"]([^'"]+)['"]/gm),
|
|
684
|
+
].map((match) => match[1]);
|
|
685
|
+
}
|
|
686
|
+
|
|
655
687
|
/**
|
|
656
688
|
* Whether a module is a CLI entrypoint rather than a library.
|
|
657
689
|
*
|
|
658
690
|
* An entrypoint is invoked by path (npm script, hook wiring, CI job), so having
|
|
659
691
|
* no importer is its normal state and says nothing about being wired.
|
|
660
692
|
*
|
|
693
|
+
* `isMainModule(` is part of the grammar because the #1371 sweep replaced ~50
|
|
694
|
+
* hand-written `process.argv[1] === import.meta.url` guards with the shared
|
|
695
|
+
* `scripts/lib/is-main-module.mjs` predicate. Without this alternative a swept
|
|
696
|
+
* CLI matches none of the other branches and silently reclassifies as a library
|
|
697
|
+
* candidate, so S4 reports it as an `unreachable-library-module` root.
|
|
698
|
+
*
|
|
661
699
|
* @param {string} body module source
|
|
662
700
|
* @returns {boolean}
|
|
663
701
|
*/
|
|
664
702
|
export function isCliEntrypoint(body) {
|
|
665
703
|
return (
|
|
666
704
|
body.startsWith('#!') ||
|
|
667
|
-
/import\.meta\.url\s*===|require\.main\s*===\s*module|process\.argv\[1\]/.test(body)
|
|
705
|
+
/import\.meta\.url\s*===|require\.main\s*===\s*module|process\.argv\[1\]|isMainModule\(/.test(body)
|
|
668
706
|
);
|
|
669
707
|
}
|
|
670
708
|
|
|
@@ -890,6 +928,10 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
890
928
|
edgeOnly,
|
|
891
929
|
entrypoint: isCliEntrypoint(body),
|
|
892
930
|
exports: collectExportedSymbols(body),
|
|
931
|
+
// Star re-exports name no symbol, so `exports` is empty for a pure
|
|
932
|
+
// barrel — see collectReExportTargets for why that silently promoted an
|
|
933
|
+
// interior module to a root.
|
|
934
|
+
reExports: collectReExportTargets(body),
|
|
893
935
|
// This file contributes NO edges — the S4 counterpart of the SELF_REL
|
|
894
936
|
// exclusion the S1/S2 corpus already applies, and for the identical
|
|
895
937
|
// reason. Every S4 `ALLOWLIST` key is a module path written here as a
|
|
@@ -947,8 +989,15 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
947
989
|
}
|
|
948
990
|
}
|
|
949
991
|
|
|
992
|
+
// A module with NO public surface at all is not a finding — nothing can be
|
|
993
|
+
// wired to it. A pure `export * from` barrel HAS a surface (everything its
|
|
994
|
+
// target exports), so it belongs in the population: otherwise it cannot be
|
|
995
|
+
// the cluster root of the module it re-exports (collectReExportTargets).
|
|
950
996
|
const unreachable = modules.filter(
|
|
951
|
-
(module) =>
|
|
997
|
+
(module) =>
|
|
998
|
+
!reachable.has(module.relative) &&
|
|
999
|
+
!module.entrypoint &&
|
|
1000
|
+
(module.exports.length > 0 || module.reExports.length > 0),
|
|
952
1001
|
);
|
|
953
1002
|
const unreachableSet = new Set(unreachable.map((module) => module.relative));
|
|
954
1003
|
// Basename census, shared by the root filter below and the downgrade half
|
|
@@ -984,6 +1033,35 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
984
1033
|
);
|
|
985
1034
|
});
|
|
986
1035
|
|
|
1036
|
+
// A pure `export *` barrel enters the population above so it can HEAD its own
|
|
1037
|
+
// cluster, but reporting it needs one more condition. S3 already exempts this
|
|
1038
|
+
// exact shape by name (`autopilot-telemetry.mjs`, condition 4): a star
|
|
1039
|
+
// re-export has zero NAMED symbols, so every "does prose name one of its
|
|
1040
|
+
// exports?" test is vacuously FALSE — including S4's own coordinator-invoked
|
|
1041
|
+
// downgrade below, which iterates `module.exports`. A reported pure barrel
|
|
1042
|
+
// could therefore never be downgraded, only allowlisted: a finding with no
|
|
1043
|
+
// legitimate exit. The category split is the barrel's TARGET. A barrel over a
|
|
1044
|
+
// module that is itself unreachable heads a genuinely dead cluster and is
|
|
1045
|
+
// reported; a barrel over LIVE code is a backward-compat shim, which is a
|
|
1046
|
+
// different (and much weaker) finding than "nothing can reach this feature".
|
|
1047
|
+
// Measured 2026-09-16: without this split `scripts/lib/autopilot-telemetry.mjs`
|
|
1048
|
+
// — `export * from './autopilot/telemetry.mjs'`, target reachable, sole
|
|
1049
|
+
// importer a test — became a new permanent WARN.
|
|
1050
|
+
// Ceiling: a dead shim over live code is now invisible to S4 as well as to S3.
|
|
1051
|
+
// Revisit if a stale back-compat shim is ever confirmed to have outlived its
|
|
1052
|
+
// last importer unnoticed — that wants its own check, not a looser S4.
|
|
1053
|
+
const reportableRoots = roots.filter((module) => {
|
|
1054
|
+
if (module.exports.length > 0) return true;
|
|
1055
|
+
return module.reExports.some((spec) => {
|
|
1056
|
+
if (!spec.startsWith('.')) {
|
|
1057
|
+
// Aliased/bare specifier: fall back to the basename granularity this
|
|
1058
|
+
// file already documents as its named residual.
|
|
1059
|
+
return [...unreachableSet].some((rel) => path.basename(rel) === path.basename(spec));
|
|
1060
|
+
}
|
|
1061
|
+
return unreachableSet.has(path.normalize(path.join(path.dirname(module.relative), spec)));
|
|
1062
|
+
});
|
|
1063
|
+
});
|
|
1064
|
+
|
|
987
1065
|
// Category split (see § Category split in the doc block above): an INSTRUCTION
|
|
988
1066
|
// document that names both the module AND one of its exported symbols is an
|
|
989
1067
|
// order addressed to a reader who will execute it — the same grammar
|
|
@@ -997,7 +1075,7 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
997
1075
|
.map((file) => ({ relative: path.relative(pluginRoot, file), body: readFileSync(file, 'utf8') }));
|
|
998
1076
|
|
|
999
1077
|
let coordinatorInvoked = 0;
|
|
1000
|
-
const findings =
|
|
1078
|
+
const findings = reportableRoots.map((module) => {
|
|
1001
1079
|
// Docs write POSIX separators regardless of host; `path.relative` does not.
|
|
1002
1080
|
const relativePosix = module.relative.split(path.sep).join('/');
|
|
1003
1081
|
const qualified = relativePosix.split('/').slice(-2).join('/');
|
|
@@ -1032,11 +1110,17 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
1032
1110
|
(token) => token !== module.base && [...unreachableSet].some((rel) => path.basename(rel) === token),
|
|
1033
1111
|
);
|
|
1034
1112
|
const tail = dragged.length > 0 ? `, and drags ${dragged.length} further unreachable module(s)` : '';
|
|
1113
|
+
// A pure barrel reports its re-export surface; naming "0 symbol(s)" there
|
|
1114
|
+
// would read as a checker bug rather than as the barrel it is.
|
|
1115
|
+
const surface =
|
|
1116
|
+
module.exports.length > 0
|
|
1117
|
+
? `exports ${module.exports.length} symbol(s) (${module.exports.slice(0, 3).join(', ')})`
|
|
1118
|
+
: `re-exports ${module.reExports.length} module(s) (${module.reExports.slice(0, 3).join(', ')})`;
|
|
1035
1119
|
return /** @type {Finding} */ ({
|
|
1036
1120
|
kind: 'unreachable-library-module',
|
|
1037
1121
|
key: module.relative,
|
|
1038
1122
|
message:
|
|
1039
|
-
|
|
1123
|
+
`${surface} but no hook, ` +
|
|
1040
1124
|
`npm script, CI job or husky stage reaches it — transitively${tail}. No instruction surface names ` +
|
|
1041
1125
|
'one of its exports either: wire it, delete it, or allowlist it with a reason',
|
|
1042
1126
|
});
|
|
@@ -1046,7 +1130,7 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
1046
1130
|
findings,
|
|
1047
1131
|
scanned: {
|
|
1048
1132
|
modules: modules.length,
|
|
1049
|
-
roots:
|
|
1133
|
+
roots: reportableRoots.length,
|
|
1050
1134
|
unreachable: unreachable.length,
|
|
1051
1135
|
coordinatorInvoked,
|
|
1052
1136
|
},
|
|
@@ -1393,7 +1477,7 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
|
|
|
1393
1477
|
return 0;
|
|
1394
1478
|
}
|
|
1395
1479
|
|
|
1396
|
-
const isMain = import.meta.url
|
|
1480
|
+
const isMain = isMainModule(import.meta.url);
|
|
1397
1481
|
if (isMain) {
|
|
1398
1482
|
const args = process.argv.slice(2);
|
|
1399
1483
|
const pluginRoot = args.find((arg) => !arg.startsWith('-'));
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
|
|
73
73
|
import { existsSync, readFileSync } from 'node:fs';
|
|
74
74
|
import path from 'node:path';
|
|
75
|
-
import { pathToFileURL } from 'node:url';
|
|
76
75
|
import { enumerateRepoFiles } from './enumerate-repo-files.mjs';
|
|
76
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
77
77
|
|
|
78
78
|
/** Marker line inside a checker's own header — declares deliberate CLI-only status. */
|
|
79
79
|
export const STANDALONE_MARKER = /^\s*\/\/\s*registration:\s*standalone\b(?:\s+(.*))?$/m;
|
|
@@ -243,7 +243,7 @@ export function runCheckValidatorRegistration(repoRoot) {
|
|
|
243
243
|
return fail > 0 ? 1 : 0;
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
const isMain =
|
|
246
|
+
const isMain =isMainModule(import.meta.url);
|
|
247
247
|
if (isMain) {
|
|
248
248
|
const root = process.argv[2];
|
|
249
249
|
if (!root) {
|
|
@@ -193,8 +193,8 @@
|
|
|
193
193
|
|
|
194
194
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
195
195
|
import path from 'node:path';
|
|
196
|
-
import { pathToFileURL } from 'node:url';
|
|
197
196
|
import { SHELL_LANGS, forEachLine } from './markdown-fences.mjs';
|
|
197
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
198
198
|
|
|
199
199
|
/** Directories whose content is scanned. Root-level `*.md` is added separately. */
|
|
200
200
|
const SCAN_DIRS = Object.freeze([
|
|
@@ -906,7 +906,7 @@ export function runCheckVcsRepoFlag(pluginRoot) {
|
|
|
906
906
|
return 0;
|
|
907
907
|
}
|
|
908
908
|
|
|
909
|
-
const isMain =
|
|
909
|
+
const isMain =isMainModule(import.meta.url);
|
|
910
910
|
if (isMain) {
|
|
911
911
|
const argv = process.argv.slice(2);
|
|
912
912
|
const flags = new Set(argv.filter((a) => a.startsWith('--')));
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
* Catches vendoring bugs that are invisible at the source-of-truth layer but
|
|
7
7
|
* become live footguns once synced into a target repo:
|
|
8
8
|
*
|
|
9
|
-
* - `paths:` frontmatter
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - `paths:` frontmatter in a `rules/` LIBRARY SOURCE — a vendoring-
|
|
10
|
+
* CONVENTION gate, not a loader-compatibility one. Since #795
|
|
11
|
+
* `rule-loader.mjs` accepts `paths:` as an alias for `globs:`, so such a
|
|
12
|
+
* rule IS glob-scoped; `globs:` stays the canonical form for a rule
|
|
13
|
+
* vendored out through the library (#742). See § Scope below — this probe
|
|
14
|
+
* never judges a repo's own consolidated `.claude/rules/` files.
|
|
12
15
|
* - Missing provenance header — `rules-sync.mjs` detects "plugin-owned vs.
|
|
13
16
|
* local override" purely by checking whether the first line starts with
|
|
14
17
|
* `PLUGIN_HEADER_PREFIX`. A source file missing that header gets
|
|
@@ -31,6 +34,25 @@
|
|
|
31
34
|
* module imports the already-exported `parseGlobsFrontmatter` from
|
|
32
35
|
* `rule-loader.mjs` directly.
|
|
33
36
|
*
|
|
37
|
+
* ## Scope: the `rules/` fleet library, never a repo's own `.claude/rules/`
|
|
38
|
+
*
|
|
39
|
+
* Every production caller feeds this module SOURCE files from the plugin's
|
|
40
|
+
* fleet library: `syncRules()` (`rules-sync.mjs`) validates
|
|
41
|
+
* `<pluginRoot>/rules/<relPath>` for each entry listed in `rules/_index.md`
|
|
42
|
+
* BEFORE writing the vendored copy into a consumer's `.claude/rules/` — the
|
|
43
|
+
* written target is never read back through the gate. The CLI's `--dir` is
|
|
44
|
+
* operator-supplied and means that same library.
|
|
45
|
+
*
|
|
46
|
+
* This is load-bearing for `paths-frontmatter`: a repo's own CONSOLIDATED rules
|
|
47
|
+
* under `.claude/rules/` are `paths:`-canonical by design, because Claude
|
|
48
|
+
* Code's native loader reads ONLY `paths:` and treats a rule without it as
|
|
49
|
+
* always-on (`validate/check-rules.mjs` #1108; `docs/rule-authoring.md`
|
|
50
|
+
* § Consolidated rules point 3). Measured 2026-09-16 in this repo: 10
|
|
51
|
+
* path-scoped rule files there, 9 of them `paths:`-only. Pointing `--dir` at
|
|
52
|
+
* such a tree would emit findings that, if obeyed, UNDO that consolidation —
|
|
53
|
+
* which is also why `check-rules.mjs` deliberately does not duplicate this
|
|
54
|
+
* probe for `.claude/rules/`.
|
|
55
|
+
*
|
|
34
56
|
* @module validate-vendored-rules
|
|
35
57
|
*/
|
|
36
58
|
|
|
@@ -40,6 +62,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
40
62
|
import { execFileSync } from 'node:child_process';
|
|
41
63
|
import { createRequire } from 'node:module';
|
|
42
64
|
import { parseGlobsFrontmatter } from './rule-loader.mjs';
|
|
65
|
+
import { isMainModule } from './is-main-module.mjs';
|
|
43
66
|
|
|
44
67
|
// Mirrors rules-sync.mjs's exported PLUGIN_HEADER_PREFIX (rules-sync.mjs
|
|
45
68
|
// line ~13). NOT imported from there on purpose: rules-sync.mjs imports
|
|
@@ -478,10 +501,17 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
|
|
|
478
501
|
// `paths:`-only rule IS glob-scoped. The probe itself stays: it enforces the canonical
|
|
479
502
|
// vendoring form, which is a convention gate, not a loader-compatibility gate. That
|
|
480
503
|
// intent survives #795 and is the subject of the #742 fleet canonicalisation sweep.
|
|
504
|
+
// SCOPE (2026-09-16): this fires only over `rules/` library sources — the module doc's
|
|
505
|
+
// § Scope section names every caller. Do NOT re-point it at a repo's own
|
|
506
|
+
// `.claude/rules/`: those consolidated files are `paths:`-canonical and obeying this
|
|
507
|
+
// remedy there would undo the consolidation (docs/rule-authoring.md point 3).
|
|
481
508
|
message:
|
|
482
509
|
`${relPath}: frontmatter declares a top-level 'paths:' key. It is a recognized alias ` +
|
|
483
510
|
`for 'globs:' (issue #795), so the rule does load glob-scoped — but 'globs:' is the ` +
|
|
484
|
-
`canonical form for
|
|
511
|
+
`canonical form for a rule VENDORED OUT through the plugin's rules/ library, which is ` +
|
|
512
|
+
`this probe's only population. Migrate to 'globs:' (see issue #742). This does NOT ` +
|
|
513
|
+
`apply to a repo's own consolidated .claude/rules/ files — those are paths:-canonical ` +
|
|
514
|
+
`(Claude Code's native loader reads only 'paths:') and this gate never scans them.`,
|
|
485
515
|
line: lineWithinFrontmatter(fm.startLine, fm.body, pathsMatch.index),
|
|
486
516
|
});
|
|
487
517
|
}
|
|
@@ -652,11 +682,7 @@ export function validateRulesDir({ dir, targetRoot = null, requireProvenance = f
|
|
|
652
682
|
|
|
653
683
|
const __filename = fileURLToPath(import.meta.url);
|
|
654
684
|
|
|
655
|
-
const isMain =
|
|
656
|
-
typeof process !== 'undefined' &&
|
|
657
|
-
process.argv[1] !== null &&
|
|
658
|
-
process.argv[1] !== undefined &&
|
|
659
|
-
resolve(process.argv[1]) === resolve(__filename);
|
|
685
|
+
const isMain =isMainModule(import.meta.url);
|
|
660
686
|
|
|
661
687
|
/**
|
|
662
688
|
* @param {{ ok: boolean, files: Array<{ file: string, violations: RuleViolation[] }>, errorCount: number, warnCount: number }} result
|
|
@@ -55,12 +55,12 @@ import {
|
|
|
55
55
|
} from 'node:fs';
|
|
56
56
|
import { homedir } from 'node:os';
|
|
57
57
|
import { join, relative, isAbsolute } from 'node:path';
|
|
58
|
-
import { pathToFileURL } from 'node:url';
|
|
59
58
|
|
|
60
59
|
import { emitEvent, sessionAttribution } from './events.mjs';
|
|
61
60
|
import { tryAcquireFileLock, releaseFileLock } from './file-lock.mjs';
|
|
62
61
|
import { readLock, isLockLive } from './session-lock.mjs';
|
|
63
62
|
import { resolveSubagentSidecar } from '../../hooks/_lib/subagent-paths.mjs';
|
|
63
|
+
import { isMainModule } from './is-main-module.mjs';
|
|
64
64
|
|
|
65
65
|
const DEFAULT_INTERVAL_S = 2;
|
|
66
66
|
const EVENTS_FILE_REL = '.orchestrator/metrics/events.jsonl';
|
|
@@ -998,6 +998,6 @@ function main() {
|
|
|
998
998
|
|
|
999
999
|
// Run only when executed as a script — importing for unit tests must not parse
|
|
1000
1000
|
// vitest's argv and exit 1.
|
|
1001
|
-
if (
|
|
1001
|
+
if (isMainModule(import.meta.url)) {
|
|
1002
1002
|
main();
|
|
1003
1003
|
}
|
package/scripts/lock-reaper.mjs
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { reapStaleLocks } from './lib/lock-reaper.mjs';
|
|
25
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
25
26
|
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
27
28
|
// Human-readable output
|
|
@@ -175,7 +176,7 @@ EXIT CODES
|
|
|
175
176
|
// CLI guard — prevents execution during test-time imports.
|
|
176
177
|
// ---------------------------------------------------------------------------
|
|
177
178
|
|
|
178
|
-
if (import.meta.url
|
|
179
|
+
if (isMainModule(import.meta.url)) {
|
|
179
180
|
main()
|
|
180
181
|
.then((code) => process.exit(code))
|
|
181
182
|
.catch((err) => {
|
|
@@ -23,10 +23,11 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
26
|
-
import { resolve } from 'node:path';
|
|
27
|
-
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { dirname, resolve } from 'node:path';
|
|
28
27
|
import { writeJsonAtomicSync } from './lib/io.mjs';
|
|
28
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
29
29
|
import { isPeerRecordId } from './lib/scope-gate.mjs';
|
|
30
|
+
import { SCOPE_MATERIALIZED_EVENT, scopeDigest } from './lib/scope-echo.mjs';
|
|
30
31
|
import {
|
|
31
32
|
MANIFEST_SESSION_KEYS,
|
|
32
33
|
manifestSessionBinding,
|
|
@@ -479,6 +480,83 @@ export function materializeWaveScope(records, {
|
|
|
479
480
|
return { aggregatePath, perAgentPaths, removedOrphans: removed, retainedOrphans: retained };
|
|
480
481
|
}
|
|
481
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Is the SEND-side half of the #1092 chain installed on this platform at all?
|
|
485
|
+
*
|
|
486
|
+
* The send-side record only exists because a `PreToolUse` hook with matcher
|
|
487
|
+
* `Agent` observes the dispatch. `hooks-codex.json` / `hooks-cursor.json` /
|
|
488
|
+
* `hooks-pi.json` deliberately carry no such entry (those platforms have no
|
|
489
|
+
* `Agent` dispatch tool — `DOCUMENTED_ASYMMETRIES`), so on them a MISSING
|
|
490
|
+
* dispatch record is not evidence of a missing injection. Recording the answer
|
|
491
|
+
* beside the wave is what lets `--verify` tell those two apart later instead of
|
|
492
|
+
* re-deriving a platform fact from an absence.
|
|
493
|
+
*
|
|
494
|
+
* Fail-CLOSED on any read error: an unreadable hooks file means the guard cannot
|
|
495
|
+
* be shown to be installed, and claiming observability we cannot prove is the
|
|
496
|
+
* direction that produces false `injection-missing` accusations.
|
|
497
|
+
*
|
|
498
|
+
* @param {string} [pluginRoot]
|
|
499
|
+
* @param {typeof readFileSync} [readFile]
|
|
500
|
+
* @returns {boolean}
|
|
501
|
+
*/
|
|
502
|
+
export function transportObservable(pluginRoot = resolve(import.meta.dirname, '..'), readFile = readFileSync) {
|
|
503
|
+
try {
|
|
504
|
+
const parsed = JSON.parse(readFile(resolve(pluginRoot, 'hooks', 'hooks.json'), 'utf8'));
|
|
505
|
+
const entries = parsed?.hooks?.PreToolUse;
|
|
506
|
+
if (!Array.isArray(entries)) return false;
|
|
507
|
+
return entries.some((entry) => typeof entry?.matcher === 'string'
|
|
508
|
+
&& entry.matcher.split('|').map((m) => m.trim()).includes('Agent'));
|
|
509
|
+
} catch {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* One `orchestrator.wave_dispatch.scope_materialized` record per wave (#1092) —
|
|
516
|
+
* the DEGRADATION half of the chain.
|
|
517
|
+
*
|
|
518
|
+
* It runs here, and not in the hook, precisely because this command runs on
|
|
519
|
+
* EVERY platform while the hook runs on one. Without it a wave with zero
|
|
520
|
+
* `scope_checked` records is ambiguous between "the coordinator skipped the
|
|
521
|
+
* manifest step" (the #1020 failure this whole chain exists for) and "this
|
|
522
|
+
* platform has no dispatch hook".
|
|
523
|
+
*
|
|
524
|
+
* `repoRoot` is the state directory's PARENT, never `process.cwd()`: the state
|
|
525
|
+
* dir is the artefact this command owns, and a caller invoking it from a
|
|
526
|
+
* subdirectory (or a test from the repo root against a `$TMPDIR` state dir)
|
|
527
|
+
* must not write its record into a different repo's ledger.
|
|
528
|
+
*
|
|
529
|
+
* Best-effort in both directions and SILENT on failure: the corpus pins
|
|
530
|
+
* byte-empty stderr on this command's success path
|
|
531
|
+
* (`tests/integration/wave-scope-producer.test.mjs`), and a telemetry write must
|
|
532
|
+
* never be the thing that fails a materialization.
|
|
533
|
+
*
|
|
534
|
+
* @param {{stateDir: string, wave: number, records: Array<{id: string, files: string[]}>}} params
|
|
535
|
+
* @returns {Promise<void>}
|
|
536
|
+
*/
|
|
537
|
+
async function emitScopeMaterialized({ stateDir, wave, records }) {
|
|
538
|
+
try {
|
|
539
|
+
const repoRoot = dirname(resolve(stateDir));
|
|
540
|
+
const agentRecords = records.filter((r) => !isPeerRecordId(r.id));
|
|
541
|
+
const digests = new Set();
|
|
542
|
+
for (const record of agentRecords) {
|
|
543
|
+
if (Array.isArray(record.files) && record.files.length > 0) digests.add(scopeDigest(record.files));
|
|
544
|
+
}
|
|
545
|
+
const { emitEvent, sessionAttribution } = await import('./lib/events.mjs');
|
|
546
|
+
await emitEvent(
|
|
547
|
+
SCOPE_MATERIALIZED_EVENT,
|
|
548
|
+
{
|
|
549
|
+
wave,
|
|
550
|
+
agent_count: agentRecords.length,
|
|
551
|
+
digest_count: digests.size,
|
|
552
|
+
transport_observable: transportObservable(),
|
|
553
|
+
...sessionAttribution(repoRoot),
|
|
554
|
+
},
|
|
555
|
+
{ repoRoot },
|
|
556
|
+
);
|
|
557
|
+
} catch { /* observability is best-effort — it never fails a materialization */ }
|
|
558
|
+
}
|
|
559
|
+
|
|
482
560
|
/**
|
|
483
561
|
* @param {string} message
|
|
484
562
|
* @param {number} code
|
|
@@ -536,6 +614,12 @@ export function main() {
|
|
|
536
614
|
? `${JSON.stringify({ ok: true, aggregatePath, perAgentPaths, removedOrphans, retainedOrphans })}\n`
|
|
537
615
|
: `${aggregatePath}\n`,
|
|
538
616
|
);
|
|
617
|
+
|
|
618
|
+
// Fire-and-forget AFTER stdout, so the telemetry append can never reorder or
|
|
619
|
+
// delay this command's own contract. Node drains the pending append before
|
|
620
|
+
// exiting because nothing here calls `process.exit()`; the `.catch` keeps an
|
|
621
|
+
// unhandled rejection from turning observability into an exit code.
|
|
622
|
+
emitScopeMaterialized({ stateDir: args.stateDir, wave: args.wave, records }).catch(() => {});
|
|
539
623
|
} catch (error) {
|
|
540
624
|
if (error instanceof InputError) {
|
|
541
625
|
fail(error.message, 1);
|
|
@@ -549,7 +633,6 @@ export function main() {
|
|
|
549
633
|
}
|
|
550
634
|
}
|
|
551
635
|
|
|
552
|
-
|
|
553
|
-
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
636
|
+
if (isMainModule(import.meta.url)) {
|
|
554
637
|
main();
|
|
555
638
|
}
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
import { readFileSync, writeFileSync, renameSync, copyFileSync, existsSync } from 'node:fs';
|
|
42
42
|
import { join, dirname, basename } from 'node:path';
|
|
43
43
|
import { validateSession, normalizeSession, ValidationError } from './lib/session-schema.mjs';
|
|
44
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
44
45
|
|
|
45
46
|
const DEFAULT_FILE = '.orchestrator/metrics/sessions.jsonl';
|
|
46
47
|
|
|
@@ -440,7 +441,7 @@ async function main() {
|
|
|
440
441
|
|
|
441
442
|
// Run main() only when this file is invoked directly as a CLI, not when imported.
|
|
442
443
|
// Prevents process.exit during test-time imports (#368).
|
|
443
|
-
if (import.meta.url
|
|
444
|
+
if (isMainModule(import.meta.url)) {
|
|
444
445
|
main().catch((err) => {
|
|
445
446
|
process.stderr.write(`migrate-sessions-jsonl: unexpected error: ${err?.stack ?? err}\n`);
|
|
446
447
|
process.exit(2);
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
|
|
51
51
|
import { promises as fs, existsSync } from 'node:fs';
|
|
52
52
|
import { spawnSync } from 'node:child_process';
|
|
53
|
-
import { pathToFileURL } from 'node:url';
|
|
54
53
|
import path from 'node:path';
|
|
55
54
|
import os from 'node:os';
|
|
56
55
|
import {
|
|
@@ -58,6 +57,7 @@ import {
|
|
|
58
57
|
VAULT_MIGRATION_RULES_PATH,
|
|
59
58
|
} from './lib/vault-migration-rules.mjs';
|
|
60
59
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
60
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
61
61
|
|
|
62
62
|
// ---------------------------------------------------------------------------
|
|
63
63
|
// Constants
|
|
@@ -773,8 +773,7 @@ function emit(opts, rec) {
|
|
|
773
773
|
// importing the module is never a hard error regardless of how the importer runs.
|
|
774
774
|
// ---------------------------------------------------------------------------
|
|
775
775
|
|
|
776
|
-
const invokedDirectly =
|
|
777
|
-
process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
776
|
+
const invokedDirectly =isMainModule(import.meta.url);
|
|
778
777
|
|
|
779
778
|
if (invokedDirectly) {
|
|
780
779
|
main().catch((err) => {
|
package/scripts/release.mjs
CHANGED
|
@@ -356,7 +356,7 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
|
|
|
356
356
|
'templates/python-uv/tests/test_sanity.py',
|
|
357
357
|
]);
|
|
358
358
|
|
|
359
|
-
// `
|
|
359
|
+
// `skills/release/SKILL.md` quotes the `npm view` OUTPUT that proves the 3.18.0 gap,
|
|
360
360
|
// dated at the line. Bumping it would destroy the evidence it exists to carry —
|
|
361
361
|
// the registry state on that date is the whole point of the paragraph.
|
|
362
362
|
//
|
|
@@ -384,7 +384,7 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
|
|
|
384
384
|
// September 10 campaign snapshot: inputs and receipt reproduce the dated 4.3-planned
|
|
385
385
|
// render made while 4.2.0 was current. Exact files only; neighboring marketing
|
|
386
386
|
// remains checked. Do not bump receipt props without a newly reviewed render.
|
|
387
|
-
export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|
|
|
387
|
+
export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|skills\/release\/SKILL\.md|marketing\/remotion\/(?:README\.md|campaign\.json|render-receipt\.json|src\/ReleaseFilm\.tsx)$)/;
|
|
388
388
|
|
|
389
389
|
/** Pure check over packed-entry lines. Returns violations: {name, line}[]. */
|
|
390
390
|
export function checkLeakage(lines) {
|
|
@@ -908,6 +908,11 @@ export function evaluateCiRow(ci) {
|
|
|
908
908
|
* "we could not read the mirror" is not "the mirror is green" (the fail-closed
|
|
909
909
|
* house rule at the top of this file).
|
|
910
910
|
*
|
|
911
|
+
* This function only judges the `ci` reading it is handed — it is
|
|
912
|
+
* `evaluateCiPreflightRows` (the caller, see its own docblock) that fetches
|
|
913
|
+
* `ci` for the commit actually being released, rather than whichever HEAD the
|
|
914
|
+
* GitHub mirror itself reports.
|
|
915
|
+
*
|
|
911
916
|
* @param {string|undefined} repoSpec — `resolveRepoSpec({vcs:'github'})`, undefined when no github remote resolves
|
|
912
917
|
* @param {null | {status?: string, failingJobName?: string, degraded?: string}} ci
|
|
913
918
|
* @returns {{ok: boolean, detail: string}}
|
|
@@ -969,6 +974,62 @@ function readPackageVersion(repoRoot) {
|
|
|
969
974
|
return JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')).version;
|
|
970
975
|
}
|
|
971
976
|
|
|
977
|
+
/**
|
|
978
|
+
* Both CI preflight rows (`ci-green-on-head` + `ci-green-on-head-github`) for
|
|
979
|
+
* ONE commit — the commit actually about to be released — never whichever
|
|
980
|
+
* HEAD the queried platform happens to report.
|
|
981
|
+
*
|
|
982
|
+
* Before this function existed, neither `checkCiStatus` call below passed
|
|
983
|
+
* `sha`, so the GitHub branch fell back to `commits/HEAD` — the MIRROR's own
|
|
984
|
+
* default-branch head, which is the release commit only once `head-pushed-github`
|
|
985
|
+
* has ALREADY proven `github/main == local HEAD`. A release cut before that
|
|
986
|
+
* push landed asked GitHub about a commit that was never pushed and could read
|
|
987
|
+
* green for work GitHub has not seen at all (measured 2026-09-16 for
|
|
988
|
+
* `3ebf0e9d`). The GitLab branch was already correct by default (`deps.sha ??
|
|
989
|
+
* getHeadSha(repoRoot)`), so only the two call sites below needed the fix —
|
|
990
|
+
* see `checkCiStatus`'s own docblock in `ci-status-banner.mjs` for the `sha`
|
|
991
|
+
* contract (#1332).
|
|
992
|
+
*
|
|
993
|
+
* Factored out of `preflight()` as its own async, DI-testable unit:
|
|
994
|
+
* `checkCiStatus` reaches the network by default, and `preflight()`'s
|
|
995
|
+
* surrounding checks (git, npm) are not test-doubled, so this is the seam
|
|
996
|
+
* through which the `sha` wiring can be unit-tested without spinning up a
|
|
997
|
+
* full fixture release.
|
|
998
|
+
*
|
|
999
|
+
* @param {string} repoRoot
|
|
1000
|
+
* @param {string} head - full hex commit id of the commit being released (the
|
|
1001
|
+
* local HEAD `preflight()` already computed for the remote-parity rows).
|
|
1002
|
+
* @param {{
|
|
1003
|
+
* skipCi?: boolean,
|
|
1004
|
+
* checkCiStatus?: Function,
|
|
1005
|
+
* resolveRepoSpec?: (opts: { repoRoot: string, vcs: 'github' }) => string|undefined,
|
|
1006
|
+
* }} [deps]
|
|
1007
|
+
* @returns {Promise<{ gitlab: {ok: boolean, detail: string}, github: {ok: boolean, detail: string} }>}
|
|
1008
|
+
*/
|
|
1009
|
+
export async function evaluateCiPreflightRows(repoRoot, head, {
|
|
1010
|
+
skipCi = false,
|
|
1011
|
+
checkCiStatus: checkCiStatusDep,
|
|
1012
|
+
resolveRepoSpec: resolveRepoSpecDep = resolveRepoSpec,
|
|
1013
|
+
} = {}) {
|
|
1014
|
+
if (skipCi) {
|
|
1015
|
+
const skipped = { ok: true, detail: 'SKIPPED via --skip-ci' };
|
|
1016
|
+
return { gitlab: skipped, github: skipped };
|
|
1017
|
+
}
|
|
1018
|
+
const checkCiStatusImpl = checkCiStatusDep ?? (await import('./lib/ci-status-banner.mjs')).checkCiStatus;
|
|
1019
|
+
|
|
1020
|
+
const ci = await checkCiStatusImpl({ repoRoot, timeoutMs: 15000, sha: head });
|
|
1021
|
+
const gitlab = evaluateCiRow(ci);
|
|
1022
|
+
|
|
1023
|
+
const githubSpec = resolveRepoSpecDep({ repoRoot, vcs: 'github' });
|
|
1024
|
+
let githubCi = null;
|
|
1025
|
+
if (githubSpec) {
|
|
1026
|
+
githubCi = await checkCiStatusImpl({ repoRoot, vcs: 'github', timeoutMs: 15000, sha: head });
|
|
1027
|
+
}
|
|
1028
|
+
const github = evaluateGithubCiRow(githubSpec, githubCi);
|
|
1029
|
+
|
|
1030
|
+
return { gitlab, github };
|
|
1031
|
+
}
|
|
1032
|
+
|
|
972
1033
|
async function preflight(repoRoot, target, { skipCi = false } = {}) {
|
|
973
1034
|
const checks = [];
|
|
974
1035
|
const add = (name, ok, detail = '') => checks.push({ name, ok, detail });
|
|
@@ -1098,39 +1159,23 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
|
|
|
1098
1159
|
add('npm-token-live', false, err.message);
|
|
1099
1160
|
}
|
|
1100
1161
|
|
|
1101
|
-
// 6. CI green on HEAD
|
|
1102
|
-
//
|
|
1103
|
-
//
|
|
1104
|
-
//
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
//
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
// detection order. The GitHub check-runs path reads `commits/HEAD`, i.e. the
|
|
1119
|
-
// mirror's default-branch head — which is HEAD only because `head-pushed-github`
|
|
1120
|
-
// above proves github/main == local HEAD; that row is this one's precondition,
|
|
1121
|
-
// not a duplicate of it.
|
|
1122
|
-
if (skipCi) {
|
|
1123
|
-
add('ci-green-on-head-github', true, 'SKIPPED via --skip-ci');
|
|
1124
|
-
} else {
|
|
1125
|
-
const githubSpec = resolveRepoSpec({ repoRoot, vcs: 'github' });
|
|
1126
|
-
let githubCi = null;
|
|
1127
|
-
if (githubSpec) {
|
|
1128
|
-
const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
|
|
1129
|
-
githubCi = await checkCiStatus({ repoRoot, vcs: 'github', timeoutMs: 15000 });
|
|
1130
|
-
}
|
|
1131
|
-
const row = evaluateGithubCiRow(githubSpec, githubCi);
|
|
1132
|
-
add('ci-green-on-head-github', row.ok, row.detail);
|
|
1133
|
-
}
|
|
1162
|
+
// 6/6b. CI green on HEAD, both platforms — judged for the commit actually
|
|
1163
|
+
// being released, never whichever HEAD the queried platform reports (see
|
|
1164
|
+
// evaluateCiPreflightRows's docblock for the `sha`-wiring history, #1332).
|
|
1165
|
+
//
|
|
1166
|
+
// GitLab: the repo's iron session-start rule applies to releases doubly —
|
|
1167
|
+
// local green is not evidence — see .claude/rules. GitHub: this second row
|
|
1168
|
+
// exists because `detectVcsFamily` picks `origin` (GitLab) for the row
|
|
1169
|
+
// above, whose pipeline is Linux-only; the macOS matrix leg lives
|
|
1170
|
+
// exclusively in `.github/workflows/test.yml`. `vcs: 'github'` forces the
|
|
1171
|
+
// probe onto the mirror without touching the detection order.
|
|
1172
|
+
// SELF-DISABLING: a checkout with no `github` remote has no mirror to be
|
|
1173
|
+
// red, so `evaluateGithubCiRow` reports `skipped` rather than red — see its
|
|
1174
|
+
// own docblock. --skip-ci is refused under --publish upstream in
|
|
1175
|
+
// validateFlags(); it can only reach this branch from --check.
|
|
1176
|
+
const ciRows = await evaluateCiPreflightRows(repoRoot, head, { skipCi });
|
|
1177
|
+
add('ci-green-on-head', ciRows.gitlab.ok, ciRows.gitlab.detail);
|
|
1178
|
+
add('ci-green-on-head-github', ciRows.github.ok, ciRows.github.detail);
|
|
1134
1179
|
|
|
1135
1180
|
// 7. Leakage gate over the actual pack file list.
|
|
1136
1181
|
// `npm_config_loglevel` is INHERITED, and `npm pack --dry-run` writes its whole
|