session-orchestrator 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/autopilot/SKILL.md +1 -0
- package/.agents/skills/bootstrap/SKILL.md +2 -0
- package/.agents/skills/brainstorm/SKILL.md +3 -0
- package/.agents/skills/close/SKILL.md +17 -0
- package/.agents/skills/debug/SKILL.md +2 -0
- package/.agents/skills/discovery/SKILL.md +2 -1
- package/.agents/skills/dispatcher/SKILL.md +2 -0
- package/.agents/skills/eli5/SKILL.md +2 -0
- package/.agents/skills/eval/SKILL.md +1 -0
- package/.agents/skills/evolve/SKILL.md +2 -1
- package/.agents/skills/go/SKILL.md +18 -0
- package/.agents/skills/grill/SKILL.md +2 -0
- package/.agents/skills/harness-audit/SKILL.md +16 -0
- package/.agents/skills/memory-cleanup/SKILL.md +1 -0
- package/.agents/skills/persona-panel/SKILL.md +1 -0
- package/.agents/skills/plan/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +17 -0
- package/.agents/skills/reconcile/SKILL.md +1 -0
- package/.agents/skills/release/SKILL.md +18 -0
- package/.agents/skills/repo-audit/SKILL.md +1 -0
- package/.agents/skills/spinout/SKILL.md +1 -0
- package/.agents/skills/sunset-review/SKILL.md +2 -0
- package/.agents/skills/test/SKILL.md +17 -0
- package/.agents/skills/ux-grill/SKILL.md +2 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/autopilot/SKILL.md +5 -4
- package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
- package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
- package/.codex-plugin/skills/close/SKILL.md +3 -3
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +2 -0
- package/.codex-plugin/skills/convergence-monitoring/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/debug/SKILL.md +11 -4
- package/.codex-plugin/skills/discovery/SKILL.md +8 -4
- package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
- package/.codex-plugin/skills/eli5/SKILL.md +9 -4
- package/.codex-plugin/skills/eval/SKILL.md +9 -4
- package/.codex-plugin/skills/evolve/SKILL.md +9 -4
- package/.codex-plugin/skills/go/SKILL.md +3 -3
- package/.codex-plugin/skills/grill/SKILL.md +11 -4
- package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
- package/.codex-plugin/skills/npm-publish/SKILL.md +2 -0
- package/.codex-plugin/skills/npm-publish/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
- package/.codex-plugin/skills/plan/SKILL.md +8 -4
- package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
- package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
- package/.codex-plugin/skills/release/SKILL.md +3 -3
- package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
- package/.codex-plugin/skills/spinout/SKILL.md +4 -4
- package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
- package/.codex-plugin/skills/test/SKILL.md +3 -3
- package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
- package/.cursor/commands/autopilot.md +4 -4
- package/.cursor/commands/bootstrap.md +5 -4
- package/.cursor/commands/brainstorm.md +5 -4
- package/.cursor/commands/close.md +4 -3
- package/.cursor/commands/convergence-monitoring.md +13 -0
- package/.cursor/commands/debug.md +4 -4
- package/.cursor/commands/discovery.md +4 -4
- package/.cursor/commands/dispatcher.md +4 -4
- package/.cursor/commands/eli5.md +4 -4
- package/.cursor/commands/eval.md +4 -4
- package/.cursor/commands/evolve.md +4 -4
- package/.cursor/commands/go.md +4 -3
- package/.cursor/commands/grill.md +4 -4
- package/.cursor/commands/harness-audit.md +3 -3
- package/.cursor/commands/memory-cleanup.md +4 -4
- package/.cursor/commands/npm-publish.md +13 -0
- package/.cursor/commands/persona-panel.md +4 -4
- package/.cursor/commands/plan.md +5 -4
- package/.cursor/commands/portfolio.md +3 -3
- package/.cursor/commands/reconcile.md +4 -4
- package/.cursor/commands/release.md +4 -3
- package/.cursor/commands/repo-audit.md +4 -4
- package/.cursor/commands/spinout.md +4 -4
- package/.cursor/commands/sunset-review.md +4 -4
- package/.cursor/commands/test.md +3 -3
- package/.cursor/commands/ux-grill.md +4 -4
- package/.cursor/rules/010-session-workflow.mdc +2 -2
- package/.cursor/skills/bootstrap/SKILL.md +1 -0
- package/.cursor/skills/close/SKILL.md +13 -0
- package/.cursor/skills/debug/SKILL.md +0 -1
- package/.cursor/skills/discovery/SKILL.md +0 -1
- package/.cursor/skills/dispatcher/SKILL.md +0 -1
- package/.cursor/skills/eli5/SKILL.md +0 -1
- package/.cursor/skills/evolve/SKILL.md +0 -1
- package/.cursor/skills/go/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +0 -1
- package/.cursor/skills/harness-audit/SKILL.md +12 -0
- package/.cursor/skills/portfolio/SKILL.md +12 -0
- package/.cursor/skills/release/SKILL.md +13 -0
- package/.cursor/skills/repo-audit/SKILL.md +0 -1
- package/.cursor/skills/sunset-review/SKILL.md +0 -1
- package/.cursor/skills/test/SKILL.md +12 -0
- package/.cursor/skills/ux-grill/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +1 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +61 -0
- package/README.md +11 -9
- package/commands/session.md +10 -0
- package/docs/ci-setup.md +53 -0
- package/docs/codex-setup.md +1 -1
- package/docs/components.md +11 -6
- package/docs/events-schema.md +4 -1
- package/docs/install.md +16 -0
- package/docs/persona-panel.md +1 -1
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +83 -14
- package/docs/scope-collision-guard.md +2 -0
- package/docs/session-config-reference.md +6 -4
- package/hooks/_lib/hook-import-set.json +46 -6
- package/hooks/_lib/subagent-paths.mjs +15 -0
- package/hooks/_lib/vcs-create-matcher.mjs +217 -62
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/on-session-end.mjs +14 -2
- package/hooks/on-stop.mjs +43 -1
- package/hooks/post-bash-write-verify.mjs +3 -0
- package/hooks/pre-auq-clarity.mjs +3 -0
- package/hooks/pre-bash-issue-budget.mjs +103 -17
- package/hooks/pre-task-scope-disjoint.mjs +152 -3
- package/hooks/skill-invocation-telemetry.mjs +2 -1
- package/package.json +2 -1
- package/pi/prompts/autopilot.md +3 -3
- package/pi/prompts/bootstrap.md +3 -3
- package/pi/prompts/brainstorm.md +3 -3
- package/pi/prompts/close.md +2 -2
- package/pi/prompts/convergence-monitoring.md +11 -0
- package/pi/prompts/debug.md +3 -3
- package/pi/prompts/discovery.md +3 -3
- package/pi/prompts/dispatcher.md +3 -3
- package/pi/prompts/eli5.md +3 -3
- package/pi/prompts/eval.md +3 -3
- package/pi/prompts/evolve.md +3 -3
- package/pi/prompts/go.md +2 -2
- package/pi/prompts/grill.md +3 -3
- package/pi/prompts/harness-audit.md +2 -3
- package/pi/prompts/memory-cleanup.md +3 -3
- package/pi/prompts/npm-publish.md +11 -0
- package/pi/prompts/persona-panel.md +3 -3
- package/pi/prompts/plan.md +3 -3
- package/pi/prompts/portfolio.md +2 -2
- package/pi/prompts/reconcile.md +3 -3
- package/pi/prompts/release.md +3 -3
- package/pi/prompts/repo-audit.md +3 -4
- package/pi/prompts/session.md +1 -1
- package/pi/prompts/spinout.md +3 -3
- package/pi/prompts/sunset-review.md +3 -3
- package/pi/prompts/templates-ack.md +1 -1
- package/pi/prompts/test.md +3 -3
- package/pi/prompts/ux-grill.md +3 -3
- package/scripts/archive-closed-prds.mjs +2 -2
- package/scripts/auq-audit.mjs +2 -3
- package/scripts/backfill-abandoned-sessions.mjs +57 -3
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +2 -2
- package/scripts/check-package-manager.mjs +2 -2
- package/scripts/ci/assert-vitest-green.mjs +2 -1
- package/scripts/emit-session.mjs +2 -3
- package/scripts/export-hw-learnings.mjs +2 -1
- package/scripts/express-path.mjs +1 -1
- package/scripts/gc-stale-worktrees.mjs +2 -1
- package/scripts/generate-codex-skills.mjs +48 -4
- package/scripts/generate-cursor-adapter.mjs +173 -9
- package/scripts/generate-hook-import-set.mjs +12 -27
- package/scripts/generate-pi-prompts.mjs +183 -13
- package/scripts/github-protection-audit.mjs +2 -3
- package/scripts/lib/agent-frontmatter.mjs +23 -1
- package/scripts/lib/claude-md-budget-lint.mjs +2 -5
- package/scripts/lib/command-blocker.mjs +133 -5
- package/scripts/lib/config/drift-check.mjs +19 -0
- package/scripts/lib/convergence-monitor.mjs +2 -2
- package/scripts/lib/cursor-hook-bridge.mjs +2 -2
- package/scripts/lib/description-surface.mjs +2 -5
- package/scripts/lib/dispatcher/cli.mjs +2 -1
- package/scripts/lib/ecosystem-wizard.mjs +2 -1
- package/scripts/lib/fetch-baseline.mjs +3 -8
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
- package/scripts/lib/instruction-budget-guard.mjs +186 -46
- package/scripts/lib/is-main-module.mjs +82 -0
- package/scripts/lib/locks/index.mjs +32 -25
- package/scripts/lib/maintenance-due-banner.mjs +69 -3
- package/scripts/lib/peer-discovery.mjs +2 -5
- package/scripts/lib/playwright-driver/runner.mjs +2 -1
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +642 -0
- package/scripts/lib/rules-sync.mjs +2 -5
- package/scripts/lib/scope-echo.mjs +392 -7
- package/scripts/lib/session-close-backfill.mjs +58 -6
- package/scripts/lib/state-md.mjs +84 -3
- package/scripts/lib/sunset/walker.mjs +31 -4
- package/scripts/lib/tests-src-ratio.mjs +2 -6
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +2 -1
- package/scripts/lib/user-invocable-skills.mjs +185 -0
- package/scripts/lib/validate/check-banner-parity.mjs +2 -2
- package/scripts/lib/validate/check-cursor-adapter.mjs +2 -2
- package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
- package/scripts/lib/validate/check-entry-guard.mjs +366 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
- package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
- package/scripts/lib/validate/check-skill-links.mjs +27 -6
- package/scripts/lib/validate/check-skill-script-paths.mjs +2 -2
- package/scripts/lib/validate/check-test-git-config-target.mjs +2 -2
- package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +2 -2
- package/scripts/lib/validate/check-unwired-features.mjs +91 -7
- package/scripts/lib/validate/check-validator-registration.mjs +2 -2
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
- package/scripts/lib/validate-vendored-rules.mjs +35 -9
- package/scripts/lib/wave-transcript-tail.mjs +2 -2
- package/scripts/lock-reaper.mjs +2 -1
- package/scripts/materialize-wave-scope.mjs +87 -4
- package/scripts/migrate-sessions-jsonl.mjs +2 -1
- package/scripts/migrate-vault-paths.mjs +2 -3
- package/scripts/release.mjs +80 -35
- package/scripts/relocate-vault-corpus.mjs +2 -3
- package/scripts/repair-invalid-sessions.mjs +2 -2
- package/scripts/session-shape.mjs +2 -2
- package/scripts/site-numbers.mjs +35 -11
- package/scripts/sweep-expired-rules.mjs +216 -0
- package/scripts/validate-plugin.mjs +9 -0
- package/scripts/vault-consolidate.mjs +2 -2
- package/scripts/vault-mirror.mjs +2 -3
- package/scripts/wave-scope-binding.mjs +2 -3
- package/skills/_shared/bootstrap-gate.md +1 -1
- package/skills/_shared/monitor-patterns.md +1 -1
- package/skills/_shared/research-evidence.md +53 -0
- package/skills/_shared/state-ownership.md +3 -0
- package/skills/autopilot/SKILL.md +58 -4
- package/skills/bootstrap/SKILL.md +51 -1
- package/skills/brainstorm/SKILL.md +16 -0
- package/skills/claude-md-drift-check/checker.mjs +49 -11
- package/{commands/close.md → skills/close/SKILL.md} +9 -3
- package/skills/debug/SKILL.md +10 -0
- package/skills/discovery/SKILL.md +24 -1
- package/skills/discovery/probes-session.md +2 -2
- package/skills/dispatcher/SKILL.md +38 -7
- package/skills/eli5/SKILL.md +11 -0
- package/skills/eval/SKILL.md +14 -0
- package/skills/evolve/SKILL.md +8 -1
- package/skills/evolve/references/evolve-dialectic-mode.md +6 -2
- package/{commands/go.md → skills/go/SKILL.md} +9 -1
- package/skills/grill/SKILL.md +19 -0
- package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
- package/skills/hook-development/SKILL.md +46 -41
- package/skills/memory-cleanup/SKILL.md +7 -0
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +56 -1
- package/skills/persona-panel/persona-format.md +1 -1
- package/skills/plan/SKILL.md +28 -1
- package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
- package/skills/reconcile/SKILL.md +10 -0
- package/{commands/release.md → skills/release/SKILL.md} +16 -2
- package/skills/repo-audit/SKILL.md +7 -0
- package/skills/session-end/plan-verification.md +2 -2
- package/skills/session-plan/SKILL.md +1 -1
- package/skills/session-start/SKILL.md +5 -4
- package/skills/session-start/phase-8-5-express-path.md +6 -6
- package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
- package/skills/spinout/SKILL.md +12 -1
- package/skills/sunset-review/SKILL.md +13 -0
- package/{commands/test.md → skills/test/SKILL.md} +10 -4
- package/skills/ux-grill/SKILL.md +19 -1
- package/skills/wave-executor/SKILL.md +7 -4
- package/skills/wave-executor/references/wave-executor-state-init.md +13 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +3 -1
- package/skills/wave-executor/references/wave-loop-review.md +17 -1
- package/commands/autopilot.md +0 -80
- package/commands/bootstrap.md +0 -56
- package/commands/brainstorm.md +0 -48
- package/commands/debug.md +0 -36
- package/commands/discovery.md +0 -32
- package/commands/dispatcher.md +0 -59
- package/commands/eli5.md +0 -33
- package/commands/eval.md +0 -28
- package/commands/evolve.md +0 -10
- package/commands/grill.md +0 -45
- package/commands/memory-cleanup.md +0 -26
- package/commands/persona-panel.md +0 -121
- package/commands/plan.md +0 -15
- package/commands/reconcile.md +0 -23
- package/commands/repo-audit.md +0 -24
- package/commands/spinout.md +0 -15
- package/commands/sunset-review.md +0 -27
- package/commands/ux-grill.md +0 -51
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-entry-guard.mjs — census of SYMLINK-FRAGILE ESM entry guards.
|
|
4
|
+
*
|
|
5
|
+
* ## The defect class (#1371)
|
|
6
|
+
*
|
|
7
|
+
* A module that runs `main()` only when invoked as a CLI compares "the module I
|
|
8
|
+
* am" against "the script Node was told to run". The two are not the same kind
|
|
9
|
+
* of path: Node resolves `import.meta.url` to the **realpath**, while
|
|
10
|
+
* `process.argv[1]` is the path **as typed**. With a symlink anywhere in the
|
|
11
|
+
* invocation path — a `node_modules/.bin/` shim, a `~/bin` convenience link, a
|
|
12
|
+
* plugin directory symlinked into `~/.claude/plugins/`, or `/tmp` itself (a
|
|
13
|
+
* symlink to `/private/tmp` on macOS) — the comparison is false.
|
|
14
|
+
*
|
|
15
|
+
* The failure mode is the worst shape a defect can have: `main()` never runs,
|
|
16
|
+
* nothing is printed, and the process exits 0. Every caller reads that as
|
|
17
|
+
* success.
|
|
18
|
+
*
|
|
19
|
+
* The fix is `isMainModule()` from `scripts/lib/is-main-module.mjs`, which
|
|
20
|
+
* realpaths BOTH sides. This check is the recurrence guard: it fails the build
|
|
21
|
+
* when a new hand-written comparison appears.
|
|
22
|
+
*
|
|
23
|
+
* ## Oracle
|
|
24
|
+
*
|
|
25
|
+
* Over every tracked `.mjs` under `scripts/` + `hooks/` (via `git ls-files`,
|
|
26
|
+
* so an untracked scratch file is never a finding):
|
|
27
|
+
*
|
|
28
|
+
* 1. strip comments (line comments, trailing `//`, and block comments),
|
|
29
|
+
* 2. split the remainder into statement fragments on `;{}`,
|
|
30
|
+
* 3. inside each fragment mentioning `process.argv[1]`, apply two oracles:
|
|
31
|
+
* a. **comparison form** — take every `===` / `!==` comparison and its two
|
|
32
|
+
* operands (bounded to the adjacent `&&` / `||` segment) and FAIL when
|
|
33
|
+
* one operand mentions `process.argv[1]` and the other mentions
|
|
34
|
+
* `import.meta.url` or `__filename`;
|
|
35
|
+
* b. **bare-basename form** — FAIL on `process.argv[1].endsWith('x.mjs')`
|
|
36
|
+
* (plus the `?.`, `String(...)` and `(argv[1] || '')` receiver
|
|
37
|
+
* variants) against a `.mjs`/`.js`/`.cjs` string literal. This form has
|
|
38
|
+
* no comparison operator at all, so oracle (a) never saw it.
|
|
39
|
+
* 4. In both cases, a fragment wrapping either side in a `realpath*` call is
|
|
40
|
+
* the safe idiom and must not be flagged.
|
|
41
|
+
*
|
|
42
|
+
* Both oracles are anchored on a SYNTAX shape, which is what keeps prose out of
|
|
43
|
+
* the census: `scripts/validate-plugin.mjs`,
|
|
44
|
+
* `scripts/lib/vault-consolidate-fs.mjs`, `hooks/post-edit-import-probe.mjs`
|
|
45
|
+
* and this file's own header all MENTION a broken idiom inside a comment and
|
|
46
|
+
* are excluded by step 1 without needing an allowlist entry.
|
|
47
|
+
* `scripts/lib/baseline-archetypes.mjs` (`await import(process.argv[1])`) is a
|
|
48
|
+
* deliberate cross-process trick that is neither a comparison nor a basename
|
|
49
|
+
* test — out of scope for this check.
|
|
50
|
+
*
|
|
51
|
+
* `scripts/lib/ecosystem-wizard.mjs` (bare `argv[1].endsWith(...)`) and
|
|
52
|
+
* `scripts/lib/fetch-baseline.mjs` (a hybrid `===`-plus-`endsWith` guard, which
|
|
53
|
+
* needed the ALLOWLIST below) were the two hand-written guards this check was
|
|
54
|
+
* built to catch (#1371); both were fixed to `isMainModule()` in #1378. Oracle
|
|
55
|
+
* (b) was added afterwards because reverting the FIRST of them was caught by
|
|
56
|
+
* nothing: the bare form was outside oracle (a) by construction, and neither
|
|
57
|
+
* file's symlink smoke test reproduced its own regression (a same-named file
|
|
58
|
+
* behind a symlinked DIRECTORY still ends with the basename). The allowlist is
|
|
59
|
+
* empty — kept as a frozen mechanism (see its docblock) rather than deleted,
|
|
60
|
+
* since the next hand-written exception is exactly what would re-populate it.
|
|
61
|
+
*
|
|
62
|
+
* `scripts/lib/is-main-module.mjs` itself is excluded: it is the fix, and its
|
|
63
|
+
* docblock quotes every variant it replaces.
|
|
64
|
+
*
|
|
65
|
+
* ## Mode: BLOCKING
|
|
66
|
+
*
|
|
67
|
+
* Unlike the WARN-only censuses in validate-plugin (`check-unwired-features`,
|
|
68
|
+
* `check-learning-provenance`, `check-vcs-repo-flag`), this one blocks. It can
|
|
69
|
+
* do so honestly because the backlog was drained to zero in the same change
|
|
70
|
+
* that introduced it — a blocking gate is only dishonest when it is red on
|
|
71
|
+
* arrival for work nobody intends to do.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
import { execFileSync } from 'node:child_process';
|
|
75
|
+
import { readFileSync } from 'node:fs';
|
|
76
|
+
import path from 'node:path';
|
|
77
|
+
|
|
78
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Files whose `process.argv[1]` comparison the oracle flags but which are NOT
|
|
82
|
+
* the defect class. Every entry carries the reason it is exempt; a stale entry
|
|
83
|
+
* (one the oracle no longer flags) is itself reported, so this list cannot rot
|
|
84
|
+
* into a silent suppression.
|
|
85
|
+
*
|
|
86
|
+
* Empty since #1378: the one entry this list ever carried
|
|
87
|
+
* (`scripts/lib/fetch-baseline.mjs`, whose hybrid guard's `endsWith(basename)`
|
|
88
|
+
* fallback made it non-fragile) was removed when that file was fixed to
|
|
89
|
+
* `isMainModule()` instead of being kept as a hand-verified exception. The
|
|
90
|
+
* mechanism is retained rather than deleted: the stale-entry check below only
|
|
91
|
+
* fires for an entry that is PRESENT and no longer flagged, so an empty object
|
|
92
|
+
* never trips it, and a future hand-written exception can still be recorded
|
|
93
|
+
* here.
|
|
94
|
+
*
|
|
95
|
+
* @type {Record<string, string>}
|
|
96
|
+
*/
|
|
97
|
+
const ALLOWLIST = {};
|
|
98
|
+
|
|
99
|
+
/** The one module allowed to contain every broken variant: it documents them. */
|
|
100
|
+
const SELF_EXEMPT = 'scripts/lib/is-main-module.mjs';
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Remove comments while preserving character offsets (so line numbers survive).
|
|
104
|
+
*
|
|
105
|
+
* @param {string} src module source
|
|
106
|
+
* @returns {string} same length, comment bytes replaced by spaces
|
|
107
|
+
*/
|
|
108
|
+
export function stripComments(src) {
|
|
109
|
+
const out = src.split('');
|
|
110
|
+
let i = 0;
|
|
111
|
+
let mode = 'code';
|
|
112
|
+
while (i < src.length) {
|
|
113
|
+
const two = src.slice(i, i + 2);
|
|
114
|
+
if (mode === 'code') {
|
|
115
|
+
if (two === '//') mode = 'line';
|
|
116
|
+
else if (two === '/*') mode = 'block';
|
|
117
|
+
else if (src[i] === '`' || src[i] === "'" || src[i] === '"') {
|
|
118
|
+
// Skip OVER the literal to its closing delimiter, leaving the contents
|
|
119
|
+
// intact. This is load-bearing, not hygiene: the V1 idiom is
|
|
120
|
+
// \`file://${process.argv[1]}\` and the `//` in `file://` reads as a line
|
|
121
|
+
// comment to a naive scanner, which blanks the rest of the line and
|
|
122
|
+
// makes the most common variant of the defect class invisible.
|
|
123
|
+
const quote = src[i];
|
|
124
|
+
i++;
|
|
125
|
+
while (i < src.length && src[i] !== quote) {
|
|
126
|
+
if (src[i] === '\\') i++;
|
|
127
|
+
i++;
|
|
128
|
+
}
|
|
129
|
+
i++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (mode === 'line') {
|
|
134
|
+
if (src[i] === '\n') mode = 'code';
|
|
135
|
+
else out[i] = ' ';
|
|
136
|
+
} else if (mode === 'block') {
|
|
137
|
+
if (src[i] !== '\n') out[i] = ' ';
|
|
138
|
+
if (two === '*/') {
|
|
139
|
+
out[i] = ' ';
|
|
140
|
+
out[i + 1] = ' ';
|
|
141
|
+
i += 2;
|
|
142
|
+
mode = 'code';
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
i++;
|
|
147
|
+
}
|
|
148
|
+
return out.join('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A `.endsWith('<something>.mjs')` call with a module-basename-shaped literal.
|
|
153
|
+
* Scanned rather than matched in one shot so the RECEIVER can be judged from the
|
|
154
|
+
* text preceding each hit — a `.endsWith()` on anything other than
|
|
155
|
+
* `process.argv[1]` is not this defect class.
|
|
156
|
+
*
|
|
157
|
+
* The quote characters are spelled `\x27` / `\x22` / `\x60` rather than
|
|
158
|
+
* literally, and that is load-bearing for THIS file specifically: `stripComments`
|
|
159
|
+
* below has no notion of a regex literal, so a bare `'` inside one reads as the
|
|
160
|
+
* start of a string and desynchronises the scanner for the rest of the module —
|
|
161
|
+
* measured 2026-09-17, a literal-quote version of this regex made the census
|
|
162
|
+
* report a line-comment 40 lines further down as a finding. Same rule applies to
|
|
163
|
+
* every regex added to this module.
|
|
164
|
+
*/
|
|
165
|
+
const ENDS_WITH_LITERAL = /endsWith\(\s*([\x27\x22\x60])([^\x27\x22\x60]*)\1\s*\)/g;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The receiver chain, whitespace-stripped, that makes an `.endsWith()` call a
|
|
169
|
+
* test on the invocation path. Deliberately unanchored at the START so an outer
|
|
170
|
+
* wrapper (`String(...)`, `path.basename(...)`) still matches; anchored at the
|
|
171
|
+
* END so only the call immediately downstream of `process.argv[1]` counts.
|
|
172
|
+
*/
|
|
173
|
+
const ARGV_RECEIVER_TAIL = /process\.argv\[1\](?:\|\|\x27\x27|\|\|\x22\x22)?\)*\??\.$/;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Findings for one module body.
|
|
177
|
+
*
|
|
178
|
+
* `kind` distinguishes the two oracles: `comparison` is the
|
|
179
|
+
* `argv[1] === import.meta.url` family, `basename` the bare
|
|
180
|
+
* `argv[1].endsWith('x.mjs')` family. Both are symlink-fragile; only the remedy
|
|
181
|
+
* wording differs.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} src module source
|
|
184
|
+
* @returns {Array<{line: number, text: string, kind: 'comparison'|'basename'}>} one per fragile guard
|
|
185
|
+
*/
|
|
186
|
+
export function findFragileGuards(src) {
|
|
187
|
+
// Template substitutions are rewritten `${x}` → `$(x)` BEFORE the fragment
|
|
188
|
+
// split: the V1 idiom `import.meta.url === \`file://${process.argv[1]}\`` puts
|
|
189
|
+
// braces INSIDE the comparison, so splitting on `{}` would tear the statement
|
|
190
|
+
// in two and the most common variant of the defect class (12 of 50 files at
|
|
191
|
+
// ca214376) would never be seen. Length is preserved so line numbers survive.
|
|
192
|
+
const stripped = stripComments(src).replace(/\$\{([^{}]*)\}/g, (_m, inner) => `$(${inner})`);
|
|
193
|
+
const findings = [];
|
|
194
|
+
let cursor = 0;
|
|
195
|
+
for (const fragment of stripped.split(/[;{}]/)) {
|
|
196
|
+
const fragStart = cursor;
|
|
197
|
+
cursor += fragment.length + 1;
|
|
198
|
+
if (!fragment.includes('process.argv[1]')) continue;
|
|
199
|
+
|
|
200
|
+
/** @type {(offset: number) => number} 1-based line of an offset in `fragment` */
|
|
201
|
+
const lineAt = (offset) =>
|
|
202
|
+
stripped.slice(0, Math.min(fragStart + offset, stripped.length)).split('\n').length;
|
|
203
|
+
|
|
204
|
+
if (!/import\.meta\.url|__filename/.test(fragment)) {
|
|
205
|
+
// Oracle (b): no self-path mention at all, so the comparison oracle below
|
|
206
|
+
// cannot see this fragment. The bare basename test lives exactly here.
|
|
207
|
+
if (!/realpath/i.test(fragment)) {
|
|
208
|
+
ENDS_WITH_LITERAL.lastIndex = 0;
|
|
209
|
+
let m;
|
|
210
|
+
while ((m = ENDS_WITH_LITERAL.exec(fragment)) !== null) {
|
|
211
|
+
if (!/\.(?:mjs|js|cjs)$/.test(m[2])) continue;
|
|
212
|
+
const receiver = fragment.slice(0, m.index).replace(/\s+/g, '');
|
|
213
|
+
if (!ARGV_RECEIVER_TAIL.test(receiver)) continue;
|
|
214
|
+
findings.push({
|
|
215
|
+
line: lineAt(m.index),
|
|
216
|
+
text: `process.argv[1]…${m[0]}`,
|
|
217
|
+
kind: 'basename',
|
|
218
|
+
});
|
|
219
|
+
break; // one finding per statement is enough to act on
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const parts = fragment.split(/===|!==/);
|
|
226
|
+
for (let k = 0; k < parts.length - 1; k++) {
|
|
227
|
+
const left = parts[k].split(/&&|\|\|/).pop() ?? '';
|
|
228
|
+
const right = parts[k + 1].split(/&&|\|\|/)[0] ?? '';
|
|
229
|
+
const mentionsArgv = /process\.argv\[1\]/.test(left) || /process\.argv\[1\]/.test(right);
|
|
230
|
+
const mentionsSelf =
|
|
231
|
+
/import\.meta\.url|__filename/.test(left) || /import\.meta\.url|__filename/.test(right);
|
|
232
|
+
if (!mentionsArgv || !mentionsSelf) continue;
|
|
233
|
+
if (/realpath/i.test(left) || /realpath/i.test(right)) continue; // the safe idiom
|
|
234
|
+
const line = lineAt(parts.slice(0, k + 1).join('===').length || 0);
|
|
235
|
+
findings.push({
|
|
236
|
+
line,
|
|
237
|
+
text: `${left.trim()} === ${right.trim()}`.replace(/\s+/g, ' '),
|
|
238
|
+
kind: 'comparison',
|
|
239
|
+
});
|
|
240
|
+
break; // one finding per statement is enough to act on
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return findings;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Tracked `.mjs` files under `scripts/` and `hooks/`.
|
|
248
|
+
*
|
|
249
|
+
* @param {string} repoRoot absolute repo root
|
|
250
|
+
* @returns {string[]} repo-relative POSIX paths
|
|
251
|
+
*/
|
|
252
|
+
function trackedModules(repoRoot) {
|
|
253
|
+
// Pathspecs are DIRECTORIES, not `scripts/**/*.mjs`: git's wildmatch requires
|
|
254
|
+
// `**` to consume at least one path component, so a `scripts/**/*.mjs` spec
|
|
255
|
+
// silently drops every top-level `scripts/*.mjs` AND all of `hooks/*.mjs` —
|
|
256
|
+
// measured 2026-09-16: 61 files matched vs 176 actually tracked. The extension
|
|
257
|
+
// filter therefore lives here, where it cannot be wrong about that.
|
|
258
|
+
//
|
|
259
|
+
// Re-measured 2026-09-17 (the 61/176 pair no longer reproduces at any scope I
|
|
260
|
+
// could reconstruct, so it is kept as-dated rather than edited):
|
|
261
|
+
// `git ls-files -- 'scripts/**/*.mjs' 'hooks/**/*.mjs' | wc -l` → 422 against
|
|
262
|
+
// `git ls-files -- scripts hooks | grep -c '\.mjs$'` → 508. Direction of the
|
|
263
|
+
// trap is unchanged; only the magnitude moved.
|
|
264
|
+
const out = execFileSync('git', ['ls-files', '--', 'scripts', 'hooks'], {
|
|
265
|
+
cwd: repoRoot,
|
|
266
|
+
encoding: 'utf8',
|
|
267
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
268
|
+
});
|
|
269
|
+
return out
|
|
270
|
+
.split('\n')
|
|
271
|
+
.map((l) => l.trim())
|
|
272
|
+
.filter((l) => l.endsWith('.mjs'));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Run the census.
|
|
277
|
+
*
|
|
278
|
+
* @param {string} repoRoot absolute repo root
|
|
279
|
+
* @returns {Promise<number>} 0 clean · 1 findings · 2 tool error
|
|
280
|
+
*/
|
|
281
|
+
export async function runCheckEntryGuard(repoRoot) {
|
|
282
|
+
let files;
|
|
283
|
+
try {
|
|
284
|
+
files = trackedModules(repoRoot);
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.log(` FAIL: cannot enumerate tracked modules: ${err.message}`);
|
|
287
|
+
console.log('');
|
|
288
|
+
console.log('Results: 0 passed, 1 failed');
|
|
289
|
+
return 2;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let failed = 0;
|
|
293
|
+
const flagged = new Set();
|
|
294
|
+
for (const rel of files) {
|
|
295
|
+
if (rel === SELF_EXEMPT) continue;
|
|
296
|
+
let body;
|
|
297
|
+
try {
|
|
298
|
+
body = readFileSync(path.join(repoRoot, rel), 'utf8');
|
|
299
|
+
} catch {
|
|
300
|
+
continue; // deleted between ls-files and read — not this check's business
|
|
301
|
+
}
|
|
302
|
+
const findings = findFragileGuards(body);
|
|
303
|
+
if (findings.length === 0) continue;
|
|
304
|
+
flagged.add(rel);
|
|
305
|
+
if (rel in ALLOWLIST) continue;
|
|
306
|
+
for (const f of findings) {
|
|
307
|
+
const why =
|
|
308
|
+
f.kind === 'basename'
|
|
309
|
+
? 'a basename test answers "was a file with this name run?", never "was THIS ' +
|
|
310
|
+
'module run" — it is true for any same-named file (a sibling copy, a ' +
|
|
311
|
+
'vendored duplicate, a fixture) and resolves no path at all, so main() ' +
|
|
312
|
+
'fires or stays silent for reasons unrelated to identity'
|
|
313
|
+
: '`import.meta.url` is the realpath, `process.argv[1]` is the path as typed, ' +
|
|
314
|
+
'so the guard is false under any symlinked invocation and main() silently ' +
|
|
315
|
+
'never runs (exit 0, no output)';
|
|
316
|
+
console.log(
|
|
317
|
+
` FAIL: ${rel}:${f.line} — symlink-fragile entry guard \`${f.text}\`: ${why}. ` +
|
|
318
|
+
'Use `isMainModule(import.meta.url)` from scripts/lib/is-main-module.mjs',
|
|
319
|
+
);
|
|
320
|
+
failed++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const censused = new Set(files);
|
|
325
|
+
for (const [rel, reason] of Object.entries(ALLOWLIST)) {
|
|
326
|
+
if (flagged.has(rel)) continue;
|
|
327
|
+
// Not present at all (a fixture repo, a fork that deleted the file) is not
|
|
328
|
+
// rot — only an entry whose file IS censused and no longer flagged is.
|
|
329
|
+
if (!censused.has(rel)) continue;
|
|
330
|
+
console.log(
|
|
331
|
+
` FAIL: ${rel} — stale allowlist entry: the oracle no longer flags this file, ` +
|
|
332
|
+
`so the exemption (“${reason}”) suppresses nothing. Remove it.`,
|
|
333
|
+
);
|
|
334
|
+
failed++;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (failed === 0) {
|
|
338
|
+
console.log(
|
|
339
|
+
` PASS: censused ${files.length} tracked module(s) under scripts/ + hooks/ — ` +
|
|
340
|
+
`0 symlink-fragile entry guard(s), ${Object.keys(ALLOWLIST).length} allowlisted`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
console.log('');
|
|
344
|
+
console.log(`Results: ${failed === 0 ? 1 : 0} passed, ${failed} failed`);
|
|
345
|
+
return failed === 0 ? 0 : 1;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (isMainModule(import.meta.url)) {
|
|
349
|
+
const argv = process.argv.slice(2);
|
|
350
|
+
const usage =
|
|
351
|
+
'Usage: check-entry-guard.mjs [<repo-root>]\n' +
|
|
352
|
+
'Exit: 0 clean · 1 symlink-fragile entry guard(s) found · 2 tool error';
|
|
353
|
+
if (argv.includes('--help')) {
|
|
354
|
+
console.log(usage);
|
|
355
|
+
process.exitCode = 0;
|
|
356
|
+
} else {
|
|
357
|
+
const positional = argv.filter((a) => !a.startsWith('--'));
|
|
358
|
+
const unknown = argv.filter((a) => a.startsWith('--') && a !== '--help');
|
|
359
|
+
if (unknown.length > 0) {
|
|
360
|
+
console.error(`Unknown flag(s): ${unknown.join(', ')}\n${usage}`);
|
|
361
|
+
process.exitCode = 1;
|
|
362
|
+
} else {
|
|
363
|
+
process.exitCode = await runCheckEntryGuard(path.resolve(positional[0] ?? process.cwd()));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
import { spawnSync } from 'node:child_process';
|
|
15
15
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
16
16
|
import path from 'node:path';
|
|
17
|
-
import { pathToFileURL } from 'node:url';
|
|
18
17
|
import { parse } from '@babel/parser';
|
|
18
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
19
19
|
|
|
20
20
|
const HOOKS_RELATIVE_DIR = 'hooks';
|
|
21
21
|
const MODULE_RELATIVE_PREFIX = 'scripts/lib';
|
|
@@ -1137,7 +1137,7 @@ export function runCheckGuardRequiresParity(pluginRoot) {
|
|
|
1137
1137
|
return inspection.toolError ? 2 : 1;
|
|
1138
1138
|
}
|
|
1139
1139
|
|
|
1140
|
-
const isMain =
|
|
1140
|
+
const isMain =isMainModule(import.meta.url);
|
|
1141
1141
|
if (isMain) {
|
|
1142
1142
|
const pluginRoot = process.argv[2];
|
|
1143
1143
|
if (!pluginRoot) {
|
|
@@ -79,9 +79,9 @@
|
|
|
79
79
|
|
|
80
80
|
import { readFileSync } from 'node:fs';
|
|
81
81
|
import path from 'node:path';
|
|
82
|
-
import { pathToFileURL } from 'node:url';
|
|
83
82
|
import { parse } from '@babel/parser';
|
|
84
83
|
import { listRepoFiles } from './repo-files.mjs';
|
|
84
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
85
85
|
|
|
86
86
|
// ---------------------------------------------------------------------------
|
|
87
87
|
// Baseline — Map<key, reason>, keyed on
|
|
@@ -359,7 +359,7 @@ export function runCheckHooksEmitEventGuard(repoRoot, options) {
|
|
|
359
359
|
return failCount > 0 ? 1 : 0;
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
const isMain =
|
|
362
|
+
const isMain =isMainModule(import.meta.url);
|
|
363
363
|
if (isMain) {
|
|
364
364
|
const root = process.argv[2];
|
|
365
365
|
if (!root) {
|
|
@@ -126,7 +126,6 @@
|
|
|
126
126
|
|
|
127
127
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
128
128
|
import path from 'node:path';
|
|
129
|
-
import { pathToFileURL } from 'node:url';
|
|
130
129
|
import { readLearnings } from '../learnings/io.mjs';
|
|
131
130
|
// THE shared key derivation. This check resolves a rule's STAMPED
|
|
132
131
|
// `learning-key` against keys it derives from the store, so it is the one place
|
|
@@ -134,6 +133,7 @@ import { readLearnings } from '../learnings/io.mjs';
|
|
|
134
133
|
// finding — it must derive the key with the emitter's function, not a copy of
|
|
135
134
|
// the emitter's formula.
|
|
136
135
|
import { learningKeyOf } from '../learnings/kebab.mjs';
|
|
136
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
137
137
|
|
|
138
138
|
/** Directory holding the rule corpus, relative to the plugin root. */
|
|
139
139
|
const RULES_REL = path.join('.claude', 'rules');
|
|
@@ -475,7 +475,7 @@ export async function runCheckLearningProvenance(pluginRoot) {
|
|
|
475
475
|
return 0;
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
-
const isMain =
|
|
478
|
+
const isMain =isMainModule(import.meta.url);
|
|
479
479
|
if (isMain) {
|
|
480
480
|
const argv = process.argv.slice(2);
|
|
481
481
|
const flags = new Set(argv.filter((a) => a.startsWith('--')));
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* citations, not intra-surface markdown links. Different predicate, different blind spot.
|
|
21
21
|
*
|
|
22
22
|
* WHAT IT CHECKS
|
|
23
|
-
* For every `.md` under the scanned surfaces, every inline link `[text](target)` whose target is
|
|
23
|
+
* For every `.md`/`.mdc` under the scanned surfaces, every inline link `[text](target)` whose target is
|
|
24
24
|
* relative (not http(s):, not mailto:, not `#anchor`, not an absolute path) must exist on disk,
|
|
25
25
|
* resolved against the LINKING FILE's own directory. A `#fragment` suffix is stripped before the
|
|
26
26
|
* existence check — anchors are out of scope (no heading index here); the path half is not.
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
44
44
|
import { join, dirname, resolve, relative, sep } from 'node:path';
|
|
45
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* Surfaces whose markdown is instruction, i.e. read and acted on.
|
|
@@ -52,8 +53,28 @@ import { join, dirname, resolve, relative, sep } from 'node:path';
|
|
|
52
53
|
* `-/issues/N` renderer links now carved out by `SKIP_TARGET`, two of them genuinely dangling
|
|
53
54
|
* links to PRDs that had been archived to the private Meta-Vault. Both classes were resolved
|
|
54
55
|
* before the widening, so the widening itself lands at 0 findings.
|
|
56
|
+
*
|
|
57
|
+
* `.cursor/rules` joined in the 3ebf0e9d fix-pass, with its yield stated honestly: that surface
|
|
58
|
+
* ships in the npm tarball and is symlinked into consumer repos by `scripts/cursor-install.mjs`,
|
|
59
|
+
* and it carried two citations of `commands/{go,close}.md` after those files were folded into
|
|
60
|
+
* their skills. Measured 2026-09-17: `.cursor/rules/*.mdc` contain ZERO `[text](target)` links
|
|
61
|
+
* (`grep -rnoE '\[[^]]*\]\([^)]+\)' .cursor/rules/` → no matches), so the widening adds 9 files
|
|
62
|
+
* and 0 checked links today and would NOT by itself have caught that defect — those citations are
|
|
63
|
+
* INLINE-CODE (`` `commands/go.md` ``), which this checker deliberately skips (see DELIBERATE
|
|
64
|
+
* NON-CHECKS). It is a forward guard for the day a real link is authored there, not the catcher
|
|
65
|
+
* for the backticked-citation class; that class has no gate in this repo (recorded, not assumed).
|
|
66
|
+
*/
|
|
67
|
+
export const SCAN_DIRS = Object.freeze(['skills', 'commands', 'agents', '.claude/rules', 'docs', '.cursor/rules']);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* File extensions this checker treats as instruction markdown.
|
|
71
|
+
*
|
|
72
|
+
* `.mdc` is Cursor's own rule extension and is the ONLY reason `.cursor/rules`
|
|
73
|
+
* is scannable at all: every file there is `<nnn>-<name>.mdc`, so a `.md`-only
|
|
74
|
+
* filter would have added the directory to {@link SCAN_DIRS} and enumerated
|
|
75
|
+
* zero files — a widening that looks live and checks nothing.
|
|
55
76
|
*/
|
|
56
|
-
export const
|
|
77
|
+
export const MD_EXTENSIONS = Object.freeze(['.md', '.mdc']);
|
|
57
78
|
|
|
58
79
|
/** Path segments that end the walk: vendored or machine-owned trees, never instruction. */
|
|
59
80
|
export const PRUNE_DIRS = new Set(['node_modules', '.git', 'coverage', 'dist', '.pnpm']);
|
|
@@ -108,8 +129,8 @@ function fencedLineNumbers(text) {
|
|
|
108
129
|
* and the check would report clean on the tree that carries the defect. This repo has the incident
|
|
109
130
|
* on record (`.claude/rules/measurement-discipline.md` § "A `git grep` drift sweep cannot see
|
|
110
131
|
* untracked files": a release sweep passed, then failed after the commit, from the same working
|
|
111
|
-
* tree with no edit in between). The cost of the filesystem walk is that a gitignored
|
|
112
|
-
* these
|
|
132
|
+
* tree with no edit in between). The cost of the filesystem walk is that a gitignored markdown file
|
|
133
|
+
* under these directories would also be checked — there are none, and one would be a finding worth
|
|
113
134
|
* seeing anyway.
|
|
114
135
|
*
|
|
115
136
|
* @param {string} repoRoot
|
|
@@ -125,7 +146,7 @@ export function listMarkdown(repoRoot, { dirs = SCAN_DIRS } = {}) {
|
|
|
125
146
|
let entries;
|
|
126
147
|
try { entries = readdirSync(abs, { recursive: true, withFileTypes: true }); } catch { continue; }
|
|
127
148
|
for (const e of entries) {
|
|
128
|
-
if (!e.isFile() || !e.name.endsWith(
|
|
149
|
+
if (!e.isFile() || !MD_EXTENSIONS.some((ext) => e.name.endsWith(ext))) continue;
|
|
129
150
|
// parentPath is absolute; make the record repo-relative with POSIX separators.
|
|
130
151
|
const rel = relative(repoRoot, join(e.parentPath ?? abs, e.name));
|
|
131
152
|
const posix = sep === '/' ? rel : rel.split(sep).join('/');
|
|
@@ -190,4 +211,4 @@ function main() {
|
|
|
190
211
|
process.stdout.write(` PASS: ${checked} relative link(s) in ${files} markdown file(s) resolve\n`);
|
|
191
212
|
}
|
|
192
213
|
|
|
193
|
-
if (import.meta.url
|
|
214
|
+
if (isMainModule(import.meta.url)) main();
|
|
@@ -100,9 +100,9 @@
|
|
|
100
100
|
|
|
101
101
|
import { existsSync, readFileSync } from 'node:fs';
|
|
102
102
|
import path from 'node:path';
|
|
103
|
-
import { pathToFileURL } from 'node:url';
|
|
104
103
|
import { enumerateRepoFiles } from './enumerate-repo-files.mjs';
|
|
105
104
|
import { forEachLine } from './markdown-fences.mjs';
|
|
105
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
106
106
|
|
|
107
107
|
/** Documentation roots whose prose is treated as a claim about the repo. */
|
|
108
108
|
export const SCAN_DIRS = Object.freeze(['skills', 'commands', 'agents', 'docs']);
|
|
@@ -461,7 +461,7 @@ export function runCheckSkillScriptPaths(pluginRoot, { strictSh = false } = {})
|
|
|
461
461
|
return inspection.ok ? 0 : 1;
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
-
const isMain =
|
|
464
|
+
const isMain =isMainModule(import.meta.url);
|
|
465
465
|
if (isMain) {
|
|
466
466
|
const strictSh = process.argv.includes('--strict-sh');
|
|
467
467
|
const args = process.argv.slice(2).filter((arg) => arg !== '--json' && arg !== '--strict-sh');
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
|
|
111
111
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
112
112
|
import path from 'node:path';
|
|
113
|
-
import {
|
|
113
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
114
114
|
|
|
115
115
|
/** Only this directory is scanned — a mutation in `scripts/` is production intent. */
|
|
116
116
|
const SCAN_DIR = 'tests';
|
|
@@ -810,7 +810,7 @@ export function runCheckTestGitConfigTarget(pluginRoot) {
|
|
|
810
810
|
return 0;
|
|
811
811
|
}
|
|
812
812
|
|
|
813
|
-
const isMain =
|
|
813
|
+
const isMain =isMainModule(import.meta.url);
|
|
814
814
|
if (isMain) {
|
|
815
815
|
const argv = process.argv.slice(2);
|
|
816
816
|
const flags = new Set(argv.filter((a) => a.startsWith('--')));
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
import { readFileSync, readdirSync, statSync, existsSync, writeFileSync } from 'node:fs';
|
|
36
36
|
import { join, extname, relative, sep } from 'node:path';
|
|
37
37
|
import { execFileSync } from 'node:child_process';
|
|
38
|
-
import {
|
|
38
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
39
39
|
|
|
40
40
|
// ---------------------------------------------------------------------------
|
|
41
41
|
// File enumeration — git ls-files primary, recursive fs walk fallback.
|
|
@@ -535,7 +535,7 @@ export function runCheckUnicodeSafety(pluginRoot, opts = {}) {
|
|
|
535
535
|
|
|
536
536
|
// CLI entry — only when executed directly, never on import (keeps the exports
|
|
537
537
|
// safe to import from tests without triggering process.exit).
|
|
538
|
-
const isMain =
|
|
538
|
+
const isMain =isMainModule(import.meta.url);
|
|
539
539
|
if (isMain) {
|
|
540
540
|
const args = process.argv.slice(2);
|
|
541
541
|
const flags = new Set(args.filter((a) => a.startsWith('--')));
|
|
@@ -104,10 +104,10 @@
|
|
|
104
104
|
// Import-safety: importing this module MUST NOT execute anything — the isMain
|
|
105
105
|
// guard at the bottom is the only side-effecting path.
|
|
106
106
|
|
|
107
|
-
import { pathToFileURL } from 'node:url';
|
|
108
107
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
109
108
|
import { spawnSync } from 'node:child_process';
|
|
110
109
|
import path from 'node:path';
|
|
110
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
111
111
|
|
|
112
112
|
// ---------------------------------------------------------------------------
|
|
113
113
|
// Constants
|
|
@@ -938,7 +938,7 @@ export function runCheckUntrackedTestDeps(repoRoot) {
|
|
|
938
938
|
return findings.length > 0 ? 1 : 0;
|
|
939
939
|
}
|
|
940
940
|
|
|
941
|
-
const isMain =
|
|
941
|
+
const isMain =isMainModule(import.meta.url);
|
|
942
942
|
if (isMain) {
|
|
943
943
|
const root = process.argv[2];
|
|
944
944
|
if (!root) {
|