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
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
import { existsSync, readFileSync } from 'node:fs';
|
|
74
74
|
import path from 'node:path';
|
|
75
75
|
import { pathToFileURL } from 'node:url';
|
|
76
|
-
import {
|
|
76
|
+
import { enumerateRepoFiles } from './enumerate-repo-files.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;
|
|
@@ -165,9 +165,15 @@ export function stripComments(text, { lineComment, blockComment, quoteChars }) {
|
|
|
165
165
|
* @returns {RegistrationResult[]} sorted by basename
|
|
166
166
|
*/
|
|
167
167
|
export function scanValidatorRegistration(repoRoot) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
// "Exists in this directory, tracked or not" (#1248) — a checker written but
|
|
169
|
+
// not yet staged is precisely the one most likely to be unregistered, and the
|
|
170
|
+
// bare git index cannot see it. `enumerateRepoFiles` still honours
|
|
171
|
+
// `.gitignore`, so no ignored tree enters the census (#1143).
|
|
172
|
+
const checkerFiles = enumerateRepoFiles({
|
|
173
|
+
repoRoot,
|
|
174
|
+
dirs: [VALIDATE_DIR_REL],
|
|
175
|
+
exts: ['mjs'],
|
|
176
|
+
}).filter((f) => /^check-.*\.mjs$/.test(path.basename(f)));
|
|
171
177
|
|
|
172
178
|
// Comment-stripped before matching (HIGH, #1184 FX-C): a basename
|
|
173
179
|
// referenced only inside a `//`/`#`/`/* *\/` comment is NOT a real
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/lib/validate/enumerate-repo-files.mjs
|
|
3
|
+
*
|
|
4
|
+
* ONE enumerator for the scanners whose question is **"does this file exist in
|
|
5
|
+
* the repository right now?"** — GitLab #1248.
|
|
6
|
+
*
|
|
7
|
+
* ## The three populations, and why this is a third one
|
|
8
|
+
*
|
|
9
|
+
* `./repo-files.mjs` already answers two questions, and this module answers a
|
|
10
|
+
* third rather than duplicating either:
|
|
11
|
+
*
|
|
12
|
+
* | function | population |
|
|
13
|
+
* |-----------------------------|-----------------------------------------------------|
|
|
14
|
+
* | `listRepoFiles()` | what git TRACKS — "will ship / is versioned" |
|
|
15
|
+
* | `listOnDiskFiles()` | what the filesystem HOLDS minus a name list |
|
|
16
|
+
* | `enumerateRepoFiles()` here | **exists under these roots, tracked or not, minus what `.gitignore` excludes** |
|
|
17
|
+
*
|
|
18
|
+
* Pick deliberately. A scanner asking "is this file versioned / will it ship?"
|
|
19
|
+
* (a packaging check, a tarball manifest) MUST keep `listRepoFiles()`. A
|
|
20
|
+
* scanner asking "is this file PRESENT, so is the citation pointing at it
|
|
21
|
+
* dead?" belongs here — for it, the index is the wrong oracle.
|
|
22
|
+
*
|
|
23
|
+
* ## Why the index is the wrong oracle for an existence check (#1248)
|
|
24
|
+
*
|
|
25
|
+
* MEASURED in a clone (Wave-1 D6, 2026-09-06): an UNTRACKED
|
|
26
|
+
* `skills/zz-probe/SKILL.md` citing `scripts/does-not-exist.mjs` made
|
|
27
|
+
* `check-skill-script-paths.mjs` report `1 passed, 0 failed` BEFORE
|
|
28
|
+
* `git add -A` and `0 passed, 1 failed` AFTER — same working tree, same
|
|
29
|
+
* defect, no edit in between. The moment a defect is most likely to exist (a
|
|
30
|
+
* brand-new, not-yet-staged skill or checker) is exactly the moment the index
|
|
31
|
+
* cannot see it, so the check reports clean on the tree that carries the bug.
|
|
32
|
+
* `.claude/rules/measurement-discipline.md` § "A `git grep` drift sweep cannot
|
|
33
|
+
* see untracked files" is the same incident class on the release sweep.
|
|
34
|
+
*
|
|
35
|
+
* ## Why NOT a bare `readdirSync` walk
|
|
36
|
+
*
|
|
37
|
+
* The naive repair — swap `git ls-files` for a filesystem walk with a fixed
|
|
38
|
+
* prune list — reintroduces the MEASURED regression #1143 that put
|
|
39
|
+
* `repo-files.mjs` there in the first place: a walk cannot see `.gitignore`,
|
|
40
|
+
* so a gitignored worktree under `.claude/worktrees/<name>` drops a COMPLETE
|
|
41
|
+
* second checkout into the census (measured with one peer worktree present:
|
|
42
|
+
* +755 `.md`, +1209 `.mjs`, 133 MB, and a peer's copy of a rule file counted
|
|
43
|
+
* as an independent document). MEASURED here 2026-09-06 @ `befdda47` on a
|
|
44
|
+
* clean tree with `.claude/worktrees/` empty:
|
|
45
|
+
*
|
|
46
|
+
* git ls-files -- skills commands agents docs → 287 .md
|
|
47
|
+
* git ls-files --cached --others
|
|
48
|
+
* --exclude-standard -- (same dirs) → 287 .md
|
|
49
|
+
* listOnDiskFiles() walk, EXCLUDED_DIRS -- (same dirs) → 290 .md
|
|
50
|
+
*
|
|
51
|
+
* The 3-file surplus of the walk is entirely gitignored content
|
|
52
|
+
* (`docs/specs/2026-04-04-plan-skill-design.md`,
|
|
53
|
+
* `docs/specs/2026-04-16-bootstrap-gate-design.md`,
|
|
54
|
+
* `docs/specs/2026-05-26-parallel-aware-sessions-design.md`) — private design
|
|
55
|
+
* notes that would enter the census as if they were repository documentation.
|
|
56
|
+
* So the oracle is `--cached --others --exclude-standard`: it sees the
|
|
57
|
+
* untracked file #1248 is about AND honours `.gitignore`, which no prune list
|
|
58
|
+
* can approximate.
|
|
59
|
+
*
|
|
60
|
+
* ## The failure contract: absent is not unreadable (#1248 follow-up)
|
|
61
|
+
*
|
|
62
|
+
* The population above is "exists under these roots". Deciding whether a path
|
|
63
|
+
* EXISTS costs one `statSync`, and that `statSync` can fail for two
|
|
64
|
+
* fundamentally different reasons which an earlier version of this module
|
|
65
|
+
* collapsed into one bare `catch { continue }`:
|
|
66
|
+
*
|
|
67
|
+
* | stat error | what it means | this module |
|
|
68
|
+
* |-----------------------------------|-------------------------------------------------|-------------|
|
|
69
|
+
* | `ENOENT` | git lists it, the checkout lacks it — sparse checkout, a deletion staged elsewhere | SKIP (contributes zero files) |
|
|
70
|
+
* | `ENOTDIR` | a parent component is a file, so the path cannot exist either | SKIP (same class) |
|
|
71
|
+
* | `EACCES` / `EPERM` | the file EXISTS; this process may not look at it | THROW {@link RepoEnumerationError} |
|
|
72
|
+
* | `ELOOP` / `EIO` / `ENAMETOOLONG` / anything else | the answer is unknown | THROW {@link RepoEnumerationError} |
|
|
73
|
+
*
|
|
74
|
+
* The skip set is deliberately the same two codes `listRepoFiles()` documents
|
|
75
|
+
* in its own filter comment ("a tracked path can be absent from the working
|
|
76
|
+
* tree"), narrowed from a catch-all to exactly the codes that mean ABSENT.
|
|
77
|
+
* Every other code means the enumerator does not know whether the file is
|
|
78
|
+
* there, and a census that silently omits a file it could not look at reports
|
|
79
|
+
* a SMALLER population than the truth — with no signal that it did.
|
|
80
|
+
*
|
|
81
|
+
* That silence had a measured consequence. `collectDriftHits()` in
|
|
82
|
+
* `scripts/release.mjs` sweeps every enumerated file for the previous release
|
|
83
|
+
* literal; with `README.md` unreadable, the swallow made the sweep read ZERO
|
|
84
|
+
* files, return `status 1` ("no match"), and `evaluateDriftSweep()` reported
|
|
85
|
+
* `ok: true` — a release gate passing on a file it could not open. Fail-closed
|
|
86
|
+
* is one line there, and it was already written:
|
|
87
|
+
*
|
|
88
|
+
* } catch (err) {
|
|
89
|
+
* return { status: 128, stdout: '', stderr: `enumerateRepoFiles failed: ${err && err.message}` };
|
|
90
|
+
*
|
|
91
|
+
* (`scripts/release.mjs:576-578`; `evaluateDriftSweep` maps any status outside
|
|
92
|
+
* {0,1} to `ok: false` — "sweep is inconclusive".) So THROWING is what turns
|
|
93
|
+
* an unreadable file into an inconclusive sweep instead of a clean one.
|
|
94
|
+
*
|
|
95
|
+
* NAMED CEILING (BV-004): only the git path fails closed. The non-git fallback
|
|
96
|
+
* walks through `listOnDiskFiles()`, whose `readdirSync` walk skips an
|
|
97
|
+
* unreadable sub-tree by design and cannot report it — see
|
|
98
|
+
* {@link enumerateRepoFiles}. Revisit trigger: the first consumer that needs
|
|
99
|
+
* fail-closed enumeration on a NON-git root (a tarball export, a vendored
|
|
100
|
+
* copy); the answer then is an error-collecting walk in `repo-files.mjs`, not
|
|
101
|
+
* a second stat pass here.
|
|
102
|
+
*
|
|
103
|
+
* ## NAMED CEILING (BV-004)
|
|
104
|
+
*
|
|
105
|
+
* The `prune` list is a fixed set of path segments / repo-relative prefixes,
|
|
106
|
+
* NOT a `.gitignore` parser — it is only load-bearing on the FALLBACK path
|
|
107
|
+
* (a root that is not a git top level: a tarball export, a vendored copy, a
|
|
108
|
+
* tmpdir fixture). On the primary path git already applies the real ignore
|
|
109
|
+
* rules and the prune list is a cheap second filter. REVISIT if a scanner
|
|
110
|
+
* ever needs this on a non-git root whose ignored trees are not covered by
|
|
111
|
+
* the default prune set — then reach for a real ignore parser, do not grow
|
|
112
|
+
* this list a sixth time.
|
|
113
|
+
*
|
|
114
|
+
* @module scripts/lib/validate/enumerate-repo-files
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
import { execFileSync } from 'node:child_process';
|
|
118
|
+
import { statSync } from 'node:fs';
|
|
119
|
+
import path from 'node:path';
|
|
120
|
+
|
|
121
|
+
import { EXCLUDED_DIRS, isGitToplevel, listOnDiskFiles } from './repo-files.mjs';
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Environment handed to `git`. An allowlist rather than `process.env`: an
|
|
125
|
+
* inherited `GIT_DIR` / `GIT_WORK_TREE` (set by any hook that spawned us)
|
|
126
|
+
* would silently re-point `ls-files` at a DIFFERENT repository, and the result
|
|
127
|
+
* would look like a plausible file list. Same allowlist as
|
|
128
|
+
* `repo-files.mjs`, which is not exported there.
|
|
129
|
+
*/
|
|
130
|
+
const GIT_ENV_ALLOWLIST = Object.freeze(['PATH', 'HOME', 'LANG', 'LC_ALL', 'TMPDIR', 'TZ']);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Path segments (or repo-relative path prefixes) never enumerated — reused
|
|
134
|
+
* from `repo-files.mjs` rather than retyped, so an addition there reaches this
|
|
135
|
+
* module too.
|
|
136
|
+
*
|
|
137
|
+
* Deliberately NOT extended with this repo's own ignored trees
|
|
138
|
+
* (`.orchestrator/tmp/`, `.claude/worktrees/`): on the primary path `git` has
|
|
139
|
+
* already applied the real ignore rules — measured, `.orchestrator/tmp/` is
|
|
140
|
+
* `.gitignore:122` — so such an entry would be dead weight there, and naming
|
|
141
|
+
* an untracked path in a module a test imports is itself a finding
|
|
142
|
+
* (`check-untracked-test-deps.mjs` R2). A caller scanning a NON-git root whose
|
|
143
|
+
* ignored subtree a basename cannot express passes it via `prune` instead —
|
|
144
|
+
* that is what the prefix form is for.
|
|
145
|
+
*/
|
|
146
|
+
export const DEFAULT_PRUNE = EXCLUDED_DIRS;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* `stat` error codes that mean the path is ABSENT from the working tree, and
|
|
150
|
+
* are therefore skipped rather than raised. See the module header's failure
|
|
151
|
+
* table for why the set is exactly these two and not a catch-all.
|
|
152
|
+
*/
|
|
153
|
+
const ABSENT_STAT_CODES = Object.freeze(new Set(['ENOENT', 'ENOTDIR']));
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Raised when a path git listed could not be RESOLVED — the file may well be
|
|
157
|
+
* there and this process could not look at it (`EACCES`, `EPERM`, `ELOOP`,
|
|
158
|
+
* `EIO`, …). Callers are expected to fail closed on it: an enumeration that
|
|
159
|
+
* threw describes no population at all.
|
|
160
|
+
*
|
|
161
|
+
* @property {string} code the underlying `stat` error code (`EACCES`, …)
|
|
162
|
+
* @property {string} path the absolute path that could not be resolved
|
|
163
|
+
*/
|
|
164
|
+
export class RepoEnumerationError extends Error {
|
|
165
|
+
/**
|
|
166
|
+
* @param {string} message
|
|
167
|
+
* @param {{code?: string, path?: string, cause?: unknown}} details
|
|
168
|
+
*/
|
|
169
|
+
constructor(message, { code, path: target, cause } = {}) {
|
|
170
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
171
|
+
this.name = 'RepoEnumerationError';
|
|
172
|
+
this.code = code;
|
|
173
|
+
this.path = target;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* True when `relative` (POSIX, repo-relative) is inside a pruned tree.
|
|
179
|
+
* A prune entry matches either a whole path SEGMENT (`node_modules`) or a
|
|
180
|
+
* repo-relative path PREFIX (`.orchestrator/tmp`).
|
|
181
|
+
*
|
|
182
|
+
* @param {string} relative repo-relative POSIX path
|
|
183
|
+
* @param {string[]} prune
|
|
184
|
+
* @returns {boolean}
|
|
185
|
+
*/
|
|
186
|
+
function isPruned(relative, prune) {
|
|
187
|
+
const segments = relative.split('/');
|
|
188
|
+
for (const entry of prune) {
|
|
189
|
+
if (entry.includes('/')) {
|
|
190
|
+
if (relative === entry || relative.startsWith(`${entry}/`)) return true;
|
|
191
|
+
} else if (segments.includes(entry)) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** @returns {NodeJS.ProcessEnv} the allowlisted git environment */
|
|
199
|
+
function gitEnv() {
|
|
200
|
+
/** @type {NodeJS.ProcessEnv} */
|
|
201
|
+
const env = {};
|
|
202
|
+
for (const key of GIT_ENV_ALLOWLIST) {
|
|
203
|
+
if (process.env[key] !== undefined) env[key] = process.env[key];
|
|
204
|
+
}
|
|
205
|
+
return env;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Normalise `exts` into a predicate over an absolute path.
|
|
210
|
+
* @param {string[] | null | undefined} exts extensions, with or without the dot
|
|
211
|
+
* @returns {(absolute: string) => boolean}
|
|
212
|
+
*/
|
|
213
|
+
function extFilter(exts) {
|
|
214
|
+
if (!exts || exts.length === 0) return () => true;
|
|
215
|
+
const set = new Set(exts.map((e) => (e.startsWith('.') ? e : `.${e}`)));
|
|
216
|
+
return (absolute) => set.has(path.extname(absolute));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Files that EXIST under `dirs` — tracked or not — minus gitignored content
|
|
221
|
+
* and minus `prune`.
|
|
222
|
+
*
|
|
223
|
+
* Drop-in shaped like `listRepoFiles()`: absolute paths, sorted, deduplicated,
|
|
224
|
+
* never throwing for a missing directory (it contributes zero files).
|
|
225
|
+
*
|
|
226
|
+
* @param {object} options
|
|
227
|
+
* @param {string} options.repoRoot absolute repository root
|
|
228
|
+
* @param {string[]} [options.dirs] repo-relative directories to scan (default: the whole root)
|
|
229
|
+
* @param {string[] | null} [options.exts] extensions to keep (default: every file)
|
|
230
|
+
* @param {string[]} [options.prune] path segments / repo-relative prefixes to skip
|
|
231
|
+
* (default: {@link DEFAULT_PRUNE}; REPLACES the default when given, so pass
|
|
232
|
+
* `[...DEFAULT_PRUNE, 'extra']` to extend it rather than to swap it)
|
|
233
|
+
* @param {(target: string) => import('node:fs').Stats} [options.stat] injection
|
|
234
|
+
* seam for tests (default: `statSync`) — the same shape `collectDriftHits()`
|
|
235
|
+
* uses for its `enumerate`/`read` seams
|
|
236
|
+
* @returns {string[]} absolute paths, sorted
|
|
237
|
+
* @throws {RepoEnumerationError} when a listed path can neither be resolved
|
|
238
|
+
* nor proven absent (`EACCES`, `EPERM`, `ELOOP`, …) — see the module header's
|
|
239
|
+
* failure table. Only the git path raises; the non-git fallback walk cannot.
|
|
240
|
+
*/
|
|
241
|
+
export function enumerateRepoFiles({
|
|
242
|
+
repoRoot,
|
|
243
|
+
dirs,
|
|
244
|
+
exts = null,
|
|
245
|
+
prune = DEFAULT_PRUNE,
|
|
246
|
+
stat = statSync,
|
|
247
|
+
} = {}) {
|
|
248
|
+
const matches = extFilter(exts);
|
|
249
|
+
const pruneList = [...prune];
|
|
250
|
+
const env = gitEnv();
|
|
251
|
+
|
|
252
|
+
if (isGitToplevel(repoRoot, env)) {
|
|
253
|
+
const pathspecs = dirs && dirs.length > 0 ? dirs.filter((d) => d !== '.') : [];
|
|
254
|
+
try {
|
|
255
|
+
// `--cached --others --exclude-standard` = tracked PLUS untracked, minus
|
|
256
|
+
// everything `.gitignore`/`.git/info/exclude` excludes. `--deduplicate`
|
|
257
|
+
// is deliberately not used: it needs git >= 2.31 and the Set below is
|
|
258
|
+
// free. See the module header for why this beats both a bare
|
|
259
|
+
// `ls-files` (#1248) and a bare walk (#1143).
|
|
260
|
+
const out = execFileSync(
|
|
261
|
+
'git',
|
|
262
|
+
['ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', ...pathspecs],
|
|
263
|
+
{
|
|
264
|
+
cwd: repoRoot,
|
|
265
|
+
encoding: 'utf8',
|
|
266
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
267
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
268
|
+
env,
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
const found = new Set();
|
|
272
|
+
for (const rel of out.split('\0')) {
|
|
273
|
+
if (!rel) continue;
|
|
274
|
+
if (isPruned(rel, pruneList)) continue;
|
|
275
|
+
const absolute = path.join(repoRoot, rel);
|
|
276
|
+
if (!matches(absolute)) continue;
|
|
277
|
+
// A tracked path can be absent from the working tree (sparse checkout,
|
|
278
|
+
// a deletion staged elsewhere). A scanner that then read it would
|
|
279
|
+
// report a tool-error for a file nobody removed — so ABSENT is skipped.
|
|
280
|
+
// Anything else means the file may be there and we could not look:
|
|
281
|
+
// fail closed rather than shrink the census in silence (header table).
|
|
282
|
+
let entry;
|
|
283
|
+
try {
|
|
284
|
+
entry = stat(absolute);
|
|
285
|
+
} catch (err) {
|
|
286
|
+
const code = err && err.code;
|
|
287
|
+
if (ABSENT_STAT_CODES.has(code)) continue;
|
|
288
|
+
throw new RepoEnumerationError(
|
|
289
|
+
`cannot stat ${absolute} (${code || 'unknown error'}): the file may exist and could not be read — enumeration is inconclusive`,
|
|
290
|
+
{ code, path: absolute, cause: err },
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
if (!entry.isFile()) continue;
|
|
294
|
+
found.add(absolute);
|
|
295
|
+
}
|
|
296
|
+
return [...found].sort();
|
|
297
|
+
} catch (err) {
|
|
298
|
+
// A resolution failure is NOT a reason to retry with a weaker oracle:
|
|
299
|
+
// the walk would skip the same unreadable entry silently and hand back a
|
|
300
|
+
// census that looks complete. Only a git/`ls-files` failure falls
|
|
301
|
+
// through — there we have no index to trust in the first place.
|
|
302
|
+
if (err instanceof RepoEnumerationError) throw err;
|
|
303
|
+
// fall through to the walk — a git that answered rev-parse but failed
|
|
304
|
+
// ls-files leaves us with no index to trust.
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Non-git root (tarball export, vendored copy, tmpdir fixture): the walk in
|
|
309
|
+
// `repo-files.mjs` is the reuse — it already skips symlinks and unreadable
|
|
310
|
+
// sub-trees. It excludes by directory BASENAME only, so prefix-shaped prune
|
|
311
|
+
// entries are re-applied here.
|
|
312
|
+
const basenamePrune = pruneList.filter((entry) => !entry.includes('/'));
|
|
313
|
+
return listOnDiskFiles(repoRoot, { dirs, exts, exclude: basenamePrune }).filter((absolute) => {
|
|
314
|
+
const rel = path.relative(repoRoot, absolute).split(path.sep).join('/');
|
|
315
|
+
return !isPruned(rel, pruneList);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
@@ -1,17 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* template.mjs — Canonical .vault.yaml template renderer for vault-backfill.
|
|
3
3
|
*
|
|
4
|
-
* Reads the template once from projects-baseline; subsequent calls
|
|
4
|
+
* Reads the template once from a projects-baseline checkout; subsequent calls
|
|
5
|
+
* use the cache. The checkout is optional — see docs/baseline.md.
|
|
5
6
|
* Part of scripts/vault-backfill.mjs (Issue #241).
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { readFileSync, existsSync } from 'node:fs';
|
|
9
10
|
import { homedir } from 'node:os';
|
|
10
|
-
import { resolve } from 'node:path';
|
|
11
|
+
import { dirname, resolve } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { resolveHostPath } from '../config/host-paths.mjs';
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
/** Path of the template RELATIVE to a projects-baseline checkout root. */
|
|
16
|
+
const TEMPLATE_REL_PATH = 'templates/shared/.vault.yaml.template';
|
|
17
|
+
|
|
18
|
+
/** This file lives at `<repoRoot>/scripts/lib/vault-backfill/`. */
|
|
19
|
+
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Candidate projects-baseline checkout roots. The baseline is optional and
|
|
23
|
+
* private (`docs/baseline.md`), so no host-specific directory name may be
|
|
24
|
+
* committed here.
|
|
25
|
+
*
|
|
26
|
+
* Two tiers, and the split is load-bearing:
|
|
27
|
+
*
|
|
28
|
+
* EXPLICIT — `PROJECTS_BASELINE_DIR`, else `SO_BASELINE_PATH` / `owner.yaml`
|
|
29
|
+
* `paths.baseline-path`. When the operator has SAID where the baseline is,
|
|
30
|
+
* that answer is used ALONE. Probing past a wrong explicit value would resolve
|
|
31
|
+
* a DIFFERENT baseline than the one named and report success — silently using
|
|
32
|
+
* a corpus nobody asked for is worse than the abort, and it would hide the
|
|
33
|
+
* typo forever.
|
|
34
|
+
*
|
|
35
|
+
* CONVENTION — the sibling checkout `scripts/sync-vault-schema.mjs` already
|
|
36
|
+
* uses, then the legacy `~/Projects` default this module shipped with. These
|
|
37
|
+
* are guesses, so probing among them is exactly right.
|
|
38
|
+
*
|
|
39
|
+
* @returns {string[]} never empty
|
|
40
|
+
*/
|
|
41
|
+
function baselineCandidates() {
|
|
42
|
+
const envDir = (process.env.PROJECTS_BASELINE_DIR || '').trim();
|
|
43
|
+
if (envDir) return [envDir];
|
|
44
|
+
const hostDir = resolveHostPath('baseline-path', null);
|
|
45
|
+
if (typeof hostDir === 'string' && hostDir.trim() !== '') return [hostDir.trim()];
|
|
46
|
+
return [
|
|
47
|
+
resolve(REPO_ROOT, '..', 'projects-baseline'),
|
|
48
|
+
resolve(homedir(), 'Projects/projects-baseline'),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolved absolute path of the canonical template.
|
|
54
|
+
*
|
|
55
|
+
* Import-time resolution is retained deliberately: the export is a plain string
|
|
56
|
+
* that `scripts/vault-backfill.mjs` and the tests both read directly. Ceiling:
|
|
57
|
+
* at most two `existsSync` calls at import. When no candidate exists the FIRST
|
|
58
|
+
* candidate is exported anyway, so `loadTemplate`'s die message names the path
|
|
59
|
+
* the operator most likely meant rather than `undefined`.
|
|
60
|
+
*/
|
|
61
|
+
export const TEMPLATE_PATH = (() => {
|
|
62
|
+
const candidates = baselineCandidates();
|
|
63
|
+
for (const base of candidates) {
|
|
64
|
+
const candidate = resolve(base, TEMPLATE_REL_PATH);
|
|
65
|
+
if (existsSync(candidate)) return candidate;
|
|
66
|
+
}
|
|
67
|
+
return resolve(candidates[0], TEMPLATE_REL_PATH);
|
|
68
|
+
})();
|
|
15
69
|
|
|
16
70
|
const TODAY = new Date().toISOString().slice(0, 10);
|
|
17
71
|
|
|
@@ -27,7 +81,10 @@ export function loadTemplate(dieFn) {
|
|
|
27
81
|
dieFn(
|
|
28
82
|
2,
|
|
29
83
|
`canonical template not found at ${TEMPLATE_PATH} — ` +
|
|
30
|
-
`
|
|
84
|
+
`the projects-baseline checkout is optional and private (see docs/baseline.md). ` +
|
|
85
|
+
`Point at it with owner.yaml \`paths.baseline-path\` (host-local, never committed), ` +
|
|
86
|
+
`the SO_BASELINE_PATH env var, or PROJECTS_BASELINE_DIR; a sibling checkout at ` +
|
|
87
|
+
`../projects-baseline is picked up automatically.`,
|
|
31
88
|
);
|
|
32
89
|
}
|
|
33
90
|
|