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
package/scripts/parse-config.mjs
CHANGED
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
* parse-config.mjs — Parse ## Session Config from CLAUDE.md or AGENTS.md and output validated JSON.
|
|
4
4
|
* Part of Session Orchestrator v3.0.0 (originally parse-config.sh in v2).
|
|
5
5
|
*
|
|
6
|
-
* Usage: node scripts/parse-config.mjs [path/to/CLAUDE.md|AGENTS.md]
|
|
6
|
+
* Usage: node scripts/parse-config.mjs [--json] [path/to/CLAUDE.md|AGENTS.md]
|
|
7
7
|
* If no path given, walks up from cwd to find project root and uses its CLAUDE.md (or AGENTS.md).
|
|
8
8
|
*
|
|
9
9
|
* Output: Single JSON object to stdout with ALL config fields (defaults applied).
|
|
10
|
+
* `--json` is an explicit NO-OP alias for that default — accepted because
|
|
11
|
+
* `docs/session-config-template.md` (lines 13 and 965) documents
|
|
12
|
+
* `node scripts/parse-config.mjs --json` as THE validation command, and the
|
|
13
|
+
* flag used to be swallowed by the positional-path slot and rejected with
|
|
14
|
+
* `File not found: --json` (exit 1). `cli-design.md` § JSON-First Output also
|
|
15
|
+
* requires every CLI to accept `--json`.
|
|
10
16
|
* Exit codes: 0 success, 1 error (message to stderr)
|
|
11
17
|
*
|
|
12
18
|
* Unparsable lines inside `## Session Config` (#1097) are reported per line on
|
|
@@ -25,6 +31,7 @@ import { join, dirname, resolve, parse as parsePath } from 'node:path';
|
|
|
25
31
|
import { fileURLToPath } from 'node:url';
|
|
26
32
|
import { parseSessionConfig } from './lib/config.mjs';
|
|
27
33
|
import { collectUnparsableLines } from './lib/config/section-extractor.mjs';
|
|
34
|
+
import { findUnterminatedComment } from './lib/config/block-preprocess.mjs';
|
|
28
35
|
import { ENFORCEMENT_VALUES } from './lib/config-schema.mjs';
|
|
29
36
|
|
|
30
37
|
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
@@ -93,10 +100,46 @@ function resolveConfigFile(argPath) {
|
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
// ---------------------------------------------------------------------------
|
|
96
|
-
// Main
|
|
103
|
+
// Main — argument parsing
|
|
97
104
|
// ---------------------------------------------------------------------------
|
|
105
|
+
//
|
|
106
|
+
// Exactly two recognised flags plus one optional positional path. `--json` is
|
|
107
|
+
// accepted and IGNORED: stdout is already a single JSON object, and the
|
|
108
|
+
// documented validation command passes the flag. Anything else starting with
|
|
109
|
+
// `-` is rejected rather than silently treated as a filename, so a typo cannot
|
|
110
|
+
// masquerade as a missing file.
|
|
111
|
+
|
|
112
|
+
const HELP_TEXT = `Usage: parse-config.mjs [--json] [path/to/CLAUDE.md|AGENTS.md]
|
|
113
|
+
|
|
114
|
+
Parse the \`## Session Config\` block from CLAUDE.md (or AGENTS.md) and write
|
|
115
|
+
the validated config to stdout as a single JSON object.
|
|
116
|
+
|
|
117
|
+
Options:
|
|
118
|
+
--json No-op alias for the default output (stdout is always JSON).
|
|
119
|
+
--help Show this help and exit 0.
|
|
120
|
+
|
|
121
|
+
Without a path, walks up from cwd to the project root and uses its CLAUDE.md,
|
|
122
|
+
falling back to AGENTS.md. Exit codes: 0 success, 1 error (message on stderr).
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
let argPath = null;
|
|
126
|
+
for (const arg of process.argv.slice(2)) {
|
|
127
|
+
if (arg === '--help' || arg === '-h') {
|
|
128
|
+
process.stdout.write(HELP_TEXT);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
} else if (arg === '--json') {
|
|
131
|
+
// no-op — the default output already IS JSON
|
|
132
|
+
} else if (arg.startsWith('-')) {
|
|
133
|
+
process.stderr.write(`parse-config.mjs: Unknown option: ${arg}\n\n${HELP_TEXT}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
} else if (argPath === null) {
|
|
136
|
+
argPath = arg;
|
|
137
|
+
} else {
|
|
138
|
+
process.stderr.write(`parse-config.mjs: Unexpected extra argument: ${arg}\n\n${HELP_TEXT}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
98
142
|
|
|
99
|
-
const argPath = process.argv[2] ?? null;
|
|
100
143
|
const configFile = resolveConfigFile(argPath);
|
|
101
144
|
|
|
102
145
|
let content;
|
|
@@ -107,6 +150,20 @@ try {
|
|
|
107
150
|
process.exit(1);
|
|
108
151
|
}
|
|
109
152
|
|
|
153
|
+
// Unterminated-comment gate — ONE line per session, not one per parser.
|
|
154
|
+
// An `<!--` with no `-->` after it would put the shared HTML-comment skipper in
|
|
155
|
+
// the swallowing state for the rest of the document; `stripHtmlCommentBlocks`
|
|
156
|
+
// now fails CLOSED (returns the lines unfiltered) rather than letting every
|
|
157
|
+
// later block disappear into its defaults, but the operator still has to learn
|
|
158
|
+
// that the document has a defect. This is the surface that says so — never
|
|
159
|
+
// fatal, exit code unchanged.
|
|
160
|
+
const unterminatedCommentLine = findUnterminatedComment(content.split(/\r?\n/));
|
|
161
|
+
if (unterminatedCommentLine !== null) {
|
|
162
|
+
process.stderr.write(
|
|
163
|
+
`⚠ ${parsePath(configFile).base}: unterminated <!-- at line ${unterminatedCommentLine} — comment stripping disabled for the whole document\n`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
110
167
|
let config;
|
|
111
168
|
try {
|
|
112
169
|
config = parseSessionConfig(content);
|
package/scripts/release.mjs
CHANGED
|
@@ -81,13 +81,14 @@ import {
|
|
|
81
81
|
rmSync,
|
|
82
82
|
realpathSync,
|
|
83
83
|
} from 'node:fs';
|
|
84
|
-
import { join } from 'node:path';
|
|
84
|
+
import { join, relative as relativePath, sep } from 'node:path';
|
|
85
85
|
import { tmpdir } from 'node:os';
|
|
86
86
|
import { spawnSync } from 'node:child_process';
|
|
87
87
|
import { parseArgs } from 'node:util';
|
|
88
88
|
import { fileURLToPath } from 'node:url';
|
|
89
89
|
|
|
90
90
|
import { resolveRepoSpec } from './lib/vcs-repo-spec.mjs';
|
|
91
|
+
import { enumerateRepoFiles } from './lib/validate/enumerate-repo-files.mjs';
|
|
91
92
|
|
|
92
93
|
const PACKAGE_NAME = 'session-orchestrator';
|
|
93
94
|
const SPAWN_OPTS = { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 };
|
|
@@ -119,6 +120,15 @@ export const SURFACES = [
|
|
|
119
120
|
file: '.claude-plugin/plugin.json',
|
|
120
121
|
patterns: [/"version":\s*"(\d+\.\d+\.\d+)"/],
|
|
121
122
|
},
|
|
123
|
+
{
|
|
124
|
+
// Root `plugin.json` — the agent-plugins.org 1.0.0 manifest, added in 4.0.0. It is a
|
|
125
|
+
// FOURTH manifest carrying the version, and it was missing from this table on the 4.0.0
|
|
126
|
+
// cut: `--set-version` bumped the other three and `validate-plugin` then failed with
|
|
127
|
+
// "plugin.json version '3.24.0' != package.json version '4.0.0'". Exactly the incident
|
|
128
|
+
// class the table exists for, caught one gate later than it should have been.
|
|
129
|
+
file: 'plugin.json',
|
|
130
|
+
patterns: [/"version":\s*"(\d+\.\d+\.\d+)"/],
|
|
131
|
+
},
|
|
122
132
|
{
|
|
123
133
|
file: '.claude-plugin/marketplace.json',
|
|
124
134
|
patterns: [/"version":\s*"(\d+\.\d+\.\d+)"/g],
|
|
@@ -307,7 +317,18 @@ function hasPathSegment(path, segment) {
|
|
|
307
317
|
|
|
308
318
|
export const LEAKAGE_PATTERNS = [
|
|
309
319
|
{ name: 'tests/', matches: (path) => hasPathSegment(path, 'tests') },
|
|
310
|
-
|
|
320
|
+
// `.orchestrator/policy/` is CARVED OUT and everything else under
|
|
321
|
+
// `.orchestrator/` still leaks: the destructive-guard FLOOR policy ships to npm
|
|
322
|
+
// consumers since 4.0.0 (Codex P1 — measured: `npm pack` carried 0 policy
|
|
323
|
+
// entries, so `loadEffectivePolicy` returned `rules:null` and the guard allowed
|
|
324
|
+
// everything on a consumer install). The carve-out is a `policy/` PATH segment,
|
|
325
|
+
// not a prefix: `.orchestrator/metrics/`, `.orchestrator/tmp/`,
|
|
326
|
+
// `.orchestrator/runtime/`, `*.lock` and the `.orchestrator/policy-backup/`
|
|
327
|
+
// prefix trick are all still caught.
|
|
328
|
+
{
|
|
329
|
+
name: '.orchestrator/',
|
|
330
|
+
matches: (path) => /\.orchestrator\//.test(path) && !/^\.orchestrator\/policy\//.test(path),
|
|
331
|
+
},
|
|
311
332
|
{ name: '.claude/', matches: (path) => hasPathSegment(path, '.claude') },
|
|
312
333
|
{ name: '.github/', matches: (path) => /\.github\//.test(path) },
|
|
313
334
|
{ name: 'node_modules', matches: (path) => /node_modules/.test(path) },
|
|
@@ -422,15 +443,55 @@ export const MIN_PACKED_ENTRIES = 400;
|
|
|
422
443
|
// ---------------------------------------------------------------------------
|
|
423
444
|
|
|
424
445
|
/**
|
|
425
|
-
*
|
|
446
|
+
* Is EVERY occurrence of `literal` on this line a dependency range (`^X.Y.Z` / `~X.Y.Z`)?
|
|
447
|
+
*
|
|
448
|
+
* One bare occurrence anywhere on the line is enough to call the whole line drift — a comment
|
|
449
|
+
* that also happens to mention a ranged dep must not be excused by that mention.
|
|
426
450
|
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
451
|
+
* @param {string} content — the matching line's text
|
|
452
|
+
* @param {string} literal — the previous release version, matched literally
|
|
453
|
+
* @returns {boolean}
|
|
454
|
+
*/
|
|
455
|
+
export function isDependencyRangeOnly(content, literal) {
|
|
456
|
+
let from = 0;
|
|
457
|
+
let seen = 0;
|
|
458
|
+
for (;;) {
|
|
459
|
+
const at = content.indexOf(literal, from);
|
|
460
|
+
if (at === -1) break;
|
|
461
|
+
seen += 1;
|
|
462
|
+
if (!(at > 0 && (content[at - 1] === '^' || content[at - 1] === '~'))) return false;
|
|
463
|
+
from = at + literal.length;
|
|
464
|
+
}
|
|
465
|
+
return seen > 0;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Drift sweep verdict over a grep-shaped result (`-l` file list or `-n` line hits).
|
|
470
|
+
*
|
|
471
|
+
* Exit-code contract, kept identical to `git grep`'s because that is what this
|
|
472
|
+
* evaluator was written against and what {@link collectDriftHits} now emits:
|
|
473
|
+
* 0 = matches found, 1 = no match (the success case here), anything else = it
|
|
474
|
+
* did not run. Measured on git 2.x: a bad regex and a bad pathspec both exit
|
|
475
|
+
* 128; git also documents 2 for usage errors. The old inline code read
|
|
476
|
+
* `.stdout` without ever looking at `.status`, so BOTH the no-match case and
|
|
477
|
+
* the it-crashed case produced an empty hit list and the same reassuring
|
|
478
|
+
* detail line, "no file still carries X". A sweep that never ran is not a
|
|
479
|
+
* clean sweep.
|
|
480
|
+
*
|
|
481
|
+
* ONE CLASS OF MATCH IS NOT OURS TO BUMP: a dependency RANGE that happens to equal our own
|
|
482
|
+
* previous version. `skills/vault-sync` pins `zod` at `^3.24.0` (the projects-baseline pin), so
|
|
483
|
+
* the 4.0.0 sweep collected two files whose literal belongs to zod and must NOT move when we
|
|
484
|
+
* release. The carve-out is deliberately a PREDICATE and not two allowlist rows: a per-path row
|
|
485
|
+
* fixes this instance and leaves the class open for the next dependency that lands on our version
|
|
486
|
+
* number — the unenumerable-table failure `.claude/rules/measurement-discipline.md` records. A
|
|
487
|
+
* caret- or tilde-prefixed literal is a range, and no version surface of this package is ever
|
|
488
|
+
* written as one (see SURFACES above: every pattern is an exact `"version": "X.Y.Z"`, `vX.Y.Z`
|
|
489
|
+
* or badge form), so the predicate cannot mask a stale surface.
|
|
490
|
+
*
|
|
491
|
+
* Accepts BOTH `git grep` output shapes. A bare `path` (from `-l`) carries no content and is
|
|
492
|
+
* therefore always a hit — the fail-closed reading, unchanged, and the shape {@link collectDriftHits}
|
|
493
|
+
* emits for a file it could not READ. `path:line:content` (from `-n`)
|
|
494
|
+
* is judged per line, and the file counts as drift as soon as ONE matching line is not a range.
|
|
434
495
|
*
|
|
435
496
|
* @param {{status: number, stdout?: string, stderr?: string}} grep
|
|
436
497
|
* @param {string} prevTag — the previous release literal being swept for
|
|
@@ -441,21 +502,102 @@ export function evaluateDriftSweep(grep, prevTag, allowlist) {
|
|
|
441
502
|
if (grep.status !== 0 && grep.status !== 1) {
|
|
442
503
|
return {
|
|
443
504
|
ok: false,
|
|
444
|
-
detail: `
|
|
505
|
+
detail: `drift sweep did not run (exit ${grep.status}): ${(grep.stderr || '').trim().slice(0, 200)} — sweep for ${prevTag} is inconclusive`,
|
|
445
506
|
};
|
|
446
507
|
}
|
|
447
|
-
const hits =
|
|
448
|
-
|
|
449
|
-
.
|
|
450
|
-
|
|
508
|
+
const hits = [];
|
|
509
|
+
for (const row of (grep.stdout || '').split('\n').filter(Boolean)) {
|
|
510
|
+
const withContent = row.match(/^(.+?):(\d+):(.*)$/);
|
|
511
|
+
const file = withContent ? withContent[1] : row;
|
|
512
|
+
if (allowlist.test(file)) continue;
|
|
513
|
+
if (withContent && isDependencyRangeOnly(withContent[3], prevTag)) continue;
|
|
514
|
+
if (!hits.includes(file)) hits.push(file);
|
|
515
|
+
}
|
|
451
516
|
return {
|
|
452
517
|
ok: hits.length === 0,
|
|
453
518
|
detail: hits.length
|
|
454
519
|
? `still carry ${prevTag}: ${hits.slice(0, 5).join(', ')}`
|
|
455
|
-
: `no
|
|
520
|
+
: `no file outside the allowlist still carries ${prevTag} (tracked + untracked-not-ignored)`,
|
|
456
521
|
};
|
|
457
522
|
}
|
|
458
523
|
|
|
524
|
+
/**
|
|
525
|
+
* Bytes of a file inspected when deciding whether it is binary.
|
|
526
|
+
*
|
|
527
|
+
* A NUL byte in the first 8 KB is the same heuristic `git grep`/`grep` use to
|
|
528
|
+
* declare a file binary. NAMED CEILING (BV-004): a text file whose only NUL
|
|
529
|
+
* sits past 8 KB is scanned as text (harmless — it produces no version match),
|
|
530
|
+
* and a binary whose first 8 KB happen to be NUL-free is scanned as text and
|
|
531
|
+
* may emit mojibake rows. Revisit trigger: the first drift-sweep row naming a
|
|
532
|
+
* file nobody recognises as text.
|
|
533
|
+
*/
|
|
534
|
+
const BINARY_SNIFF_BYTES = 8192;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Produce the drift-sweep hit list over the files that EXIST in the working
|
|
538
|
+
* tree — tracked or not — in the exact `{status, stdout, stderr}` shape
|
|
539
|
+
* {@link evaluateDriftSweep} already consumes.
|
|
540
|
+
*
|
|
541
|
+
* ## Why not `git grep` any more (#1248)
|
|
542
|
+
*
|
|
543
|
+
* `git grep` searches the INDEX: it is blind to an untracked file, so the
|
|
544
|
+
* moment a stale version literal is most likely to exist (a doc or manifest
|
|
545
|
+
* written for this release and not yet staged) is exactly the moment the sweep
|
|
546
|
+
* cannot see it and reports clean. The population is now
|
|
547
|
+
* `scripts/lib/validate/enumerate-repo-files.mjs`
|
|
548
|
+
* (`git ls-files --cached --others --exclude-standard`) — tracked PLUS
|
|
549
|
+
* untracked, minus everything `.gitignore` excludes, so `node_modules/` and
|
|
550
|
+
* the peer worktrees under `.claude/worktrees/` stay out without a prune list
|
|
551
|
+
* having to guess at them.
|
|
552
|
+
*
|
|
553
|
+
* What `git grep` bought — searching hidden directories a plain `rg` skips,
|
|
554
|
+
* which is how the forgotten `.codex-plugin` manifest was found — is kept:
|
|
555
|
+
* `git ls-files` lists dotted paths like any other.
|
|
556
|
+
*
|
|
557
|
+
* ## Fail-closed, in two places
|
|
558
|
+
*
|
|
559
|
+
* Enumeration itself throwing is reported as exit 128, which
|
|
560
|
+
* {@link evaluateDriftSweep} reads as "inconclusive" = FAIL — the same reading
|
|
561
|
+
* a crashed `git grep` got. A single file that enumerates but cannot be READ
|
|
562
|
+
* is emitted as a CONTENT-LESS row, which that evaluator already treats as a
|
|
563
|
+
* hit: a file we could not sweep is never silently a clean file.
|
|
564
|
+
*
|
|
565
|
+
* @param {object} options
|
|
566
|
+
* @param {string} options.repoRoot absolute repository root
|
|
567
|
+
* @param {string} options.prevTag the previous release literal to sweep for
|
|
568
|
+
* @param {(o: {repoRoot: string}) => string[]} [options.enumerate] injection seam for tests
|
|
569
|
+
* @param {(absolute: string) => Buffer} [options.read] injection seam for tests
|
|
570
|
+
* @returns {{status: number, stdout: string, stderr: string}}
|
|
571
|
+
*/
|
|
572
|
+
export function collectDriftHits({ repoRoot, prevTag, enumerate = enumerateRepoFiles, read = readFileSync }) {
|
|
573
|
+
let files;
|
|
574
|
+
try {
|
|
575
|
+
files = enumerate({ repoRoot });
|
|
576
|
+
} catch (err) {
|
|
577
|
+
return { status: 128, stdout: '', stderr: `enumerateRepoFiles failed: ${err && err.message}` };
|
|
578
|
+
}
|
|
579
|
+
const rows = [];
|
|
580
|
+
for (const absolute of files) {
|
|
581
|
+
const relative = relativePath(repoRoot, absolute).split(sep).join('/');
|
|
582
|
+
let buf;
|
|
583
|
+
try {
|
|
584
|
+
buf = read(absolute);
|
|
585
|
+
} catch {
|
|
586
|
+
// Content-less row = hit (see the evaluator's contract above).
|
|
587
|
+
rows.push(relative);
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
if (buf.subarray(0, BINARY_SNIFF_BYTES).includes(0)) continue;
|
|
591
|
+
const text = buf.toString('utf8');
|
|
592
|
+
if (!text.includes(prevTag)) continue;
|
|
593
|
+
const lines = text.split('\n');
|
|
594
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
595
|
+
if (lines[i].includes(prevTag)) rows.push(`${relative}:${i + 1}:${lines[i]}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return { status: rows.length ? 0 : 1, stdout: rows.length ? `${rows.join('\n')}\n` : '', stderr: '' };
|
|
599
|
+
}
|
|
600
|
+
|
|
459
601
|
/**
|
|
460
602
|
* Registry-collision verdict over `npm view <pkg> versions --json`.
|
|
461
603
|
*
|
|
@@ -589,6 +731,53 @@ export function evaluateNpmAuth(whoami) {
|
|
|
589
731
|
: { ok: false, detail: 'npm whoami exited 0 with an empty identity — the token could not be confirmed' };
|
|
590
732
|
}
|
|
591
733
|
|
|
734
|
+
/**
|
|
735
|
+
* Turn a `checkCiStatus` result into the `ci-green-on-head` preflight row.
|
|
736
|
+
*
|
|
737
|
+
* Three input states since #1031, and the middle one is why this is a named
|
|
738
|
+
* function rather than a ternary: `degraded` means the check could NOT be READ.
|
|
739
|
+
* Interpolating `ci.status` there printed `status: undefined` — a red row whose
|
|
740
|
+
* detail names no cause, which reads as "CI is broken" when the truth is "we
|
|
741
|
+
* never found out". Only an actual `status: 'green'` reading passes; a release
|
|
742
|
+
* must never proceed on an unknown CI state.
|
|
743
|
+
*
|
|
744
|
+
* @param {null | {status?: string, failingJobName?: string, degraded?: string}} ci
|
|
745
|
+
* @returns {{ok: boolean, detail: string}}
|
|
746
|
+
*/
|
|
747
|
+
export function evaluateCiRow(ci) {
|
|
748
|
+
if (ci === null || ci === undefined) return { ok: false, detail: 'CI status unavailable' };
|
|
749
|
+
if (ci.degraded) return { ok: false, detail: `CI status unknown (${ci.degraded})` };
|
|
750
|
+
const job = ci.failingJobName ? ` (${ci.failingJobName})` : '';
|
|
751
|
+
return { ok: ci.status === 'green', detail: `status: ${ci.status}${job}` };
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Turn the GitHub-mirror CI reading into the `ci-green-on-head-github` row.
|
|
756
|
+
*
|
|
757
|
+
* WHY A SECOND CI ROW AT ALL: `ci-green-on-head` reads whatever platform
|
|
758
|
+
* `detectVcsFamily` picks for `origin` — GitLab here — and the GitLab pipeline
|
|
759
|
+
* runs Linux only. The **macOS** matrix leg exists solely on the GitHub mirror
|
|
760
|
+
* (`.github/workflows/test.yml`), i.e. on the operator's own platform. A
|
|
761
|
+
* release could therefore go out fully green with the macOS leg red, and the
|
|
762
|
+
* release path would never have asked.
|
|
763
|
+
*
|
|
764
|
+
* SELF-DISABLING, and that is the one place this row is allowed to pass without
|
|
765
|
+
* evidence: a checkout with no `github` remote has no mirror to be red, so the
|
|
766
|
+
* row is `skipped` rather than red. Everything else keeps the three-state
|
|
767
|
+
* contract of `evaluateCiRow` unchanged — `unknown` and `degraded` FAIL, because
|
|
768
|
+
* "we could not read the mirror" is not "the mirror is green" (the fail-closed
|
|
769
|
+
* house rule at the top of this file).
|
|
770
|
+
*
|
|
771
|
+
* @param {string|undefined} repoSpec — `resolveRepoSpec({vcs:'github'})`, undefined when no github remote resolves
|
|
772
|
+
* @param {null | {status?: string, failingJobName?: string, degraded?: string}} ci
|
|
773
|
+
* @returns {{ok: boolean, detail: string}}
|
|
774
|
+
*/
|
|
775
|
+
export function evaluateGithubCiRow(repoSpec, ci) {
|
|
776
|
+
if (!repoSpec) return { ok: true, detail: 'skipped — no github remote' };
|
|
777
|
+
const row = evaluateCiRow(ci);
|
|
778
|
+
return { ok: row.ok, detail: `${repoSpec} — ${row.detail}` };
|
|
779
|
+
}
|
|
780
|
+
|
|
592
781
|
/**
|
|
593
782
|
* Flag-combination gate, applied before any work.
|
|
594
783
|
*
|
|
@@ -697,12 +886,18 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
|
|
|
697
886
|
const changelog = checkChangelogEntry(readFileSync(join(repoRoot, 'CHANGELOG.md'), 'utf8'), target);
|
|
698
887
|
add('changelog-entry', changelog.ok, changelog.problems.join('; '));
|
|
699
888
|
|
|
700
|
-
// 3b. Drift sweep: no
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
//
|
|
704
|
-
//
|
|
705
|
-
//
|
|
889
|
+
// 3b. Drift sweep: no file outside the surfaces table + allowlist may still
|
|
890
|
+
// carry the previous release's version literal.
|
|
891
|
+
//
|
|
892
|
+
// POPULATION (#1248, closed here): this used to be one `git grep`, which
|
|
893
|
+
// searches the INDEX and is therefore blind to UNTRACKED files — the sweep
|
|
894
|
+
// measured "no TRACKED file still carries X" while its row read as a
|
|
895
|
+
// whole-tree census. It now enumerates via
|
|
896
|
+
// `scripts/lib/validate/enumerate-repo-files.mjs` (tracked PLUS
|
|
897
|
+
// untracked-not-ignored) and greps in-process; see {@link collectDriftHits}
|
|
898
|
+
// for the fail-closed contract and why hidden directories (the forgotten
|
|
899
|
+
// .codex-plugin manifest) are still covered. Allowlisted: files that
|
|
900
|
+
// legitimately carry version HISTORY.
|
|
706
901
|
const tagList = run('git', ['tag', '-l', 'v*', '--sort=-v:refname'], { cwd: repoRoot });
|
|
707
902
|
const prevTag = (tagList.stdout || '')
|
|
708
903
|
.split('\n').map((t) => t.trim().replace(/^v/, ''))
|
|
@@ -712,8 +907,11 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
|
|
|
712
907
|
// one of them means the sweep is unnecessary.
|
|
713
908
|
add('drift-sweep', false, `git tag -l failed (exit ${tagList.status}) — cannot determine the previous release to sweep for`);
|
|
714
909
|
} else if (prevTag) {
|
|
715
|
-
|
|
716
|
-
|
|
910
|
+
// Line-shaped rows (not a bare file list): the verdict needs the matching LINE, because a
|
|
911
|
+
// caret-ranged dependency that equals our previous version is not drift and a file list
|
|
912
|
+
// cannot show that.
|
|
913
|
+
const hits = collectDriftHits({ repoRoot, prevTag });
|
|
914
|
+
const sweep = evaluateDriftSweep(hits, prevTag, HISTORY_ALLOWLIST);
|
|
717
915
|
add('drift-sweep', sweep.ok, sweep.detail);
|
|
718
916
|
} else {
|
|
719
917
|
add('drift-sweep', true, 'no previous tag to sweep against');
|
|
@@ -769,8 +967,29 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
|
|
|
769
967
|
} else {
|
|
770
968
|
const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
|
|
771
969
|
const ci = await checkCiStatus({ repoRoot, timeoutMs: 15000 });
|
|
772
|
-
const
|
|
773
|
-
add('ci-green-on-head',
|
|
970
|
+
const row = evaluateCiRow(ci);
|
|
971
|
+
add('ci-green-on-head', row.ok, row.detail);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// 6b. CI green on the GitHub mirror too. The row above asks the platform
|
|
975
|
+
// `detectVcsFamily` picks for `origin` (GitLab), whose pipeline is Linux-only;
|
|
976
|
+
// the macOS matrix leg lives exclusively in `.github/workflows/test.yml`.
|
|
977
|
+
// `vcs: 'github'` forces the probe onto the mirror without touching the
|
|
978
|
+
// detection order. The GitHub check-runs path reads `commits/HEAD`, i.e. the
|
|
979
|
+
// mirror's default-branch head — which is HEAD only because `head-pushed-github`
|
|
980
|
+
// above proves github/main == local HEAD; that row is this one's precondition,
|
|
981
|
+
// not a duplicate of it.
|
|
982
|
+
if (skipCi) {
|
|
983
|
+
add('ci-green-on-head-github', true, 'SKIPPED via --skip-ci');
|
|
984
|
+
} else {
|
|
985
|
+
const githubSpec = resolveRepoSpec({ repoRoot, vcs: 'github' });
|
|
986
|
+
let githubCi = null;
|
|
987
|
+
if (githubSpec) {
|
|
988
|
+
const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
|
|
989
|
+
githubCi = await checkCiStatus({ repoRoot, vcs: 'github', timeoutMs: 15000 });
|
|
990
|
+
}
|
|
991
|
+
const row = evaluateGithubCiRow(githubSpec, githubCi);
|
|
992
|
+
add('ci-green-on-head-github', row.ok, row.detail);
|
|
774
993
|
}
|
|
775
994
|
|
|
776
995
|
// 7. Leakage gate over the actual pack file list.
|
|
@@ -952,6 +1171,37 @@ export function waitForRegistryPropagation(repoRoot, target, deps = {}) {
|
|
|
952
1171
|
* @param {{runImpl?: Function, waitImpl?: Function, attempts?: number, delaySeconds?: number}} [deps]
|
|
953
1172
|
* @returns {{receipt: {confirmed: boolean, target: string, detail: string}, propagation: ReturnType<typeof waitForRegistryPropagation>}}
|
|
954
1173
|
*/
|
|
1174
|
+
/**
|
|
1175
|
+
* The exact `npm publish` invocation: argv plus spawn options.
|
|
1176
|
+
*
|
|
1177
|
+
* WHY THE ENV PIN, AND WHY THIS IS A NAMED FUNCTION: `npm_config_loglevel` is
|
|
1178
|
+
* INHERITED, and the `+ <pkg>@<version>` receipt line — the ONE piece of
|
|
1179
|
+
* evidence `evaluatePublishReceipt` accepts as the irreversible boundary — is
|
|
1180
|
+
* printed at `notice` level. Any ancestor that ran under `npm run --silent`
|
|
1181
|
+
* (the husky pre-push gate does exactly that) therefore hands this child a
|
|
1182
|
+
* silent loglevel, npm publishes successfully and prints nothing, the receipt
|
|
1183
|
+
* reads as unconfirmed, `publish()` throws, and `main()` exits 2 = "pre-receipt,
|
|
1184
|
+
* safe to rerun" while the registry already holds the version. That is the worst
|
|
1185
|
+
* failure this file can produce, and it is the same inherited-silent trap the
|
|
1186
|
+
* leakage gate's `npm pack --dry-run` was already pinned against (see preflight
|
|
1187
|
+
* step 7). The pin makes the receipt independent of the caller's environment.
|
|
1188
|
+
*
|
|
1189
|
+
* Exported because `publish()` itself deliberately is not (it is the
|
|
1190
|
+
* irreversible act) — the invocation it builds is pure, so the pin is testable
|
|
1191
|
+
* without a publishable seam.
|
|
1192
|
+
*
|
|
1193
|
+
* @param {string} repoRoot
|
|
1194
|
+
* @param {string} userconfigPath — the 0600 temp npmrc carrying the token
|
|
1195
|
+
* @returns {{cmd: string, args: string[], opts: {cwd: string, env: object}}}
|
|
1196
|
+
*/
|
|
1197
|
+
export function publishInvocation(repoRoot, userconfigPath) {
|
|
1198
|
+
return {
|
|
1199
|
+
cmd: 'npm',
|
|
1200
|
+
args: ['publish', '--access', 'public', '--userconfig', userconfigPath],
|
|
1201
|
+
opts: { cwd: repoRoot, env: { ...process.env, npm_config_loglevel: 'notice' } },
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
|
|
955
1205
|
// Deliberately NOT exported: this is the irreversible act, and every production
|
|
956
1206
|
// path to it runs through main() -> preflight() (leakage gate, CI gate, dirty-tree
|
|
957
1207
|
// gate). Exporting it made the whole gate chain bypassable by any importer, and no
|
|
@@ -959,9 +1209,10 @@ export function waitForRegistryPropagation(repoRoot, target, deps = {}) {
|
|
|
959
1209
|
function publish(repoRoot, target, deps = {}) {
|
|
960
1210
|
const token = loadNpmToken(repoRoot);
|
|
961
1211
|
const runImpl = deps.runImpl ?? run;
|
|
962
|
-
const res = withTempUserconfig(token, (tmpRc) =>
|
|
963
|
-
|
|
964
|
-
|
|
1212
|
+
const res = withTempUserconfig(token, (tmpRc) => {
|
|
1213
|
+
const call = publishInvocation(repoRoot, tmpRc);
|
|
1214
|
+
return runImpl(call.cmd, call.args, call.opts);
|
|
1215
|
+
});
|
|
965
1216
|
const receipt = evaluatePublishReceipt(res, target);
|
|
966
1217
|
if (!receipt.confirmed) throw new Error(receipt.detail);
|
|
967
1218
|
|
|
@@ -1099,6 +1350,46 @@ export async function runPublishRelease(repoRoot, target, deps = {}) {
|
|
|
1099
1350
|
};
|
|
1100
1351
|
}
|
|
1101
1352
|
|
|
1353
|
+
/**
|
|
1354
|
+
* Render the partial tag/push state left behind by a failed post-receipt tail.
|
|
1355
|
+
*
|
|
1356
|
+
* Pure so the reconciliation lines are unit-testable against a synthetic
|
|
1357
|
+
* outcome: the branch that produces them only exists after an irreversible npm
|
|
1358
|
+
* publish, which no test may perform.
|
|
1359
|
+
*
|
|
1360
|
+
* Absent progress is reported as absent, never as "nothing happened": a
|
|
1361
|
+
* `tagAndPushImpl` that threw before attaching `releaseProgress` leaves state
|
|
1362
|
+
* genuinely unknown, and the operator must inspect rather than assume.
|
|
1363
|
+
*
|
|
1364
|
+
* @param {{tag?: string|null, localTagCreated?: boolean, remotes?: Array<{remote: string, mainPushed?: boolean, tagPushed?: boolean}>}} [tagProgress]
|
|
1365
|
+
* @returns {string[]}
|
|
1366
|
+
*/
|
|
1367
|
+
export function describeTagProgress(tagProgress) {
|
|
1368
|
+
const lines = ['\nPARTIAL TAG/PUSH STATE (the npm receipt is already irreversible):'];
|
|
1369
|
+
if (!tagProgress || typeof tagProgress !== 'object') {
|
|
1370
|
+
lines.push(' tag/push progress was not recorded — inspect `git tag -l` and both remotes manually.');
|
|
1371
|
+
return lines;
|
|
1372
|
+
}
|
|
1373
|
+
const tag = tagProgress.tag || 'the release tag';
|
|
1374
|
+
lines.push(
|
|
1375
|
+
tagProgress.localTagCreated === true
|
|
1376
|
+
? ` local tag ${tag}: CREATED (the next \`--check\` will fail \`tag-free-local\` until it is pushed or deleted).`
|
|
1377
|
+
: ` local tag ${tag}: not created.`,
|
|
1378
|
+
);
|
|
1379
|
+
const remotes = Array.isArray(tagProgress.remotes) ? tagProgress.remotes : [];
|
|
1380
|
+
if (remotes.length === 0) {
|
|
1381
|
+
lines.push(' no remote was reached — neither main nor the tag was pushed anywhere.');
|
|
1382
|
+
return lines;
|
|
1383
|
+
}
|
|
1384
|
+
for (const remote of remotes) {
|
|
1385
|
+
lines.push(
|
|
1386
|
+
` ${remote.remote}: main ${remote.mainPushed === true ? 'pushed' : 'NOT pushed'}, ` +
|
|
1387
|
+
`tag ${remote.tagPushed === true ? 'pushed' : 'NOT pushed'}.`,
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
return lines;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1102
1393
|
/**
|
|
1103
1394
|
* Print a completed publish-tail outcome and return the CLI exit code.
|
|
1104
1395
|
*
|
|
@@ -1120,6 +1411,16 @@ export function printPublishOutcome(outcome, target, io = {}) {
|
|
|
1120
1411
|
}
|
|
1121
1412
|
if (!tagAndPushFailed) {
|
|
1122
1413
|
log(` tagged ${outcome.tag} (AFTER publish) and pushed main+tag to: ${outcome.pushed.join(', ')}.`);
|
|
1414
|
+
} else {
|
|
1415
|
+
// The npm receipt is already irreversible at this point, so the ONLY thing
|
|
1416
|
+
// that helps the operator is the exact partial state tag-and-push reached.
|
|
1417
|
+
// `runPublishRelease` collects it (`tagProgress.localTagCreated` plus a
|
|
1418
|
+
// per-remote `{mainPushed, tagPushed}`); this printer used to reference
|
|
1419
|
+
// none of it and suppressed the `pushed:` line as well, so the operator was
|
|
1420
|
+
// told only THAT it failed. The local-tag line matters twice over: a
|
|
1421
|
+
// created local tag makes the next `--check` fail `tag-free-local`, which
|
|
1422
|
+
// reads as a mysterious collision unless it was announced here.
|
|
1423
|
+
for (const line of describeTagProgress(outcome.tagProgress)) error(line);
|
|
1123
1424
|
}
|
|
1124
1425
|
|
|
1125
1426
|
if (outcome.release.skipped) {
|
|
@@ -1377,6 +1678,13 @@ async function main() {
|
|
|
1377
1678
|
if (!values.publish) return 0;
|
|
1378
1679
|
|
|
1379
1680
|
console.log(`\nPublishing ${PACKAGE_NAME}@${target} ...`);
|
|
1681
|
+
// No spawn timeout is set anywhere in this file, on purpose: a kill in the
|
|
1682
|
+
// middle of `npm publish` or a tag push is the very failure the receipt
|
|
1683
|
+
// boundary exists to avoid. The wall-clock cost is real, though — each of
|
|
1684
|
+
// the two `git push` remotes re-runs the husky pre-push full gate (~2.5 min
|
|
1685
|
+
// each, measured) and the site poll waits up to 120 s.
|
|
1686
|
+
console.log(' This tail can run ~7 minutes (pre-push gate x2 remotes + up to 120s site poll).');
|
|
1687
|
+
console.log(' Run it with a >=600s command timeout or in the background — do NOT kill it mid-run.');
|
|
1380
1688
|
const outcome = await runPublishRelease(repoRoot, target, { publishImpl: publish });
|
|
1381
1689
|
return printPublishOutcome(outcome, target);
|
|
1382
1690
|
}
|
|
@@ -40,7 +40,7 @@ import path from 'node:path';
|
|
|
40
40
|
import { fileURLToPath } from 'node:url';
|
|
41
41
|
|
|
42
42
|
import { repairLedger, CANONICAL_LEDGER_REL } from './lib/session-record-repair.mjs';
|
|
43
|
-
import {
|
|
43
|
+
import { getProjectDir } from './lib/platform.mjs';
|
|
44
44
|
|
|
45
45
|
const USAGE =
|
|
46
46
|
'Usage: node scripts/repair-invalid-sessions.mjs [--dry-run|--apply] [--json]\n' +
|
|
@@ -52,7 +52,7 @@ const USAGE =
|
|
|
52
52
|
CANONICAL_LEDGER_REL +
|
|
53
53
|
');\n' +
|
|
54
54
|
' must resolve inside <repo-root>/.orchestrator/metrics/\n' +
|
|
55
|
-
' --repo-root project root (default: resolved
|
|
55
|
+
' --repo-root project root (default: resolved project dir)\n' +
|
|
56
56
|
'Exit codes: 0 completed, 1 arg error, 2 system error, 3 post-verification failed\n';
|
|
57
57
|
|
|
58
58
|
/**
|
|
@@ -168,7 +168,7 @@ async function main() {
|
|
|
168
168
|
|
|
169
169
|
// --apply is an explicit opt-in; absent it (or with --dry-run) we never write.
|
|
170
170
|
const apply = values.apply === true && values['dry-run'] !== true;
|
|
171
|
-
const repoRoot = values['repo-root'] ||
|
|
171
|
+
const repoRoot = values['repo-root'] || getProjectDir();
|
|
172
172
|
const requestedFile = values.file || path.join(repoRoot, CANONICAL_LEDGER_REL);
|
|
173
173
|
|
|
174
174
|
// MED-3: bound the target to <repoRoot>/.orchestrator/metrics/ BEFORE any I/O.
|