session-orchestrator 5.0.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 +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +2 -2
- 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 +2 -2
- package/.orchestrator/policy/blocked-commands.json +10 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +80 -0
- package/README.md +74 -235
- package/commands/session.md +10 -0
- package/docs/USER-GUIDE.md +24 -0
- package/docs/ci-setup.md +53 -0
- package/docs/codex-setup.md +1 -1
- package/docs/components.md +12 -5
- package/docs/events-schema.md +5 -1
- package/docs/install.md +128 -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/docs/session-config-template.md +38 -0
- package/docs/telemetry.md +15 -0
- 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/enforce-scope.mjs +42 -1
- 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 +3 -2
- 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 +209 -9
- 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 +63 -2
- 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 +266 -11
- 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 +124 -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/skills/playwright-driver/SKILL.md +7 -10
- 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
|
@@ -23,10 +23,11 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
26
|
-
import { resolve } from 'node:path';
|
|
27
|
-
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { dirname, resolve } from 'node:path';
|
|
28
27
|
import { writeJsonAtomicSync } from './lib/io.mjs';
|
|
28
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
29
29
|
import { isPeerRecordId } from './lib/scope-gate.mjs';
|
|
30
|
+
import { SCOPE_MATERIALIZED_EVENT, scopeDigest } from './lib/scope-echo.mjs';
|
|
30
31
|
import {
|
|
31
32
|
MANIFEST_SESSION_KEYS,
|
|
32
33
|
manifestSessionBinding,
|
|
@@ -479,6 +480,83 @@ export function materializeWaveScope(records, {
|
|
|
479
480
|
return { aggregatePath, perAgentPaths, removedOrphans: removed, retainedOrphans: retained };
|
|
480
481
|
}
|
|
481
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Is the SEND-side half of the #1092 chain installed on this platform at all?
|
|
485
|
+
*
|
|
486
|
+
* The send-side record only exists because a `PreToolUse` hook with matcher
|
|
487
|
+
* `Agent` observes the dispatch. `hooks-codex.json` / `hooks-cursor.json` /
|
|
488
|
+
* `hooks-pi.json` deliberately carry no such entry (those platforms have no
|
|
489
|
+
* `Agent` dispatch tool — `DOCUMENTED_ASYMMETRIES`), so on them a MISSING
|
|
490
|
+
* dispatch record is not evidence of a missing injection. Recording the answer
|
|
491
|
+
* beside the wave is what lets `--verify` tell those two apart later instead of
|
|
492
|
+
* re-deriving a platform fact from an absence.
|
|
493
|
+
*
|
|
494
|
+
* Fail-CLOSED on any read error: an unreadable hooks file means the guard cannot
|
|
495
|
+
* be shown to be installed, and claiming observability we cannot prove is the
|
|
496
|
+
* direction that produces false `injection-missing` accusations.
|
|
497
|
+
*
|
|
498
|
+
* @param {string} [pluginRoot]
|
|
499
|
+
* @param {typeof readFileSync} [readFile]
|
|
500
|
+
* @returns {boolean}
|
|
501
|
+
*/
|
|
502
|
+
export function transportObservable(pluginRoot = resolve(import.meta.dirname, '..'), readFile = readFileSync) {
|
|
503
|
+
try {
|
|
504
|
+
const parsed = JSON.parse(readFile(resolve(pluginRoot, 'hooks', 'hooks.json'), 'utf8'));
|
|
505
|
+
const entries = parsed?.hooks?.PreToolUse;
|
|
506
|
+
if (!Array.isArray(entries)) return false;
|
|
507
|
+
return entries.some((entry) => typeof entry?.matcher === 'string'
|
|
508
|
+
&& entry.matcher.split('|').map((m) => m.trim()).includes('Agent'));
|
|
509
|
+
} catch {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* One `orchestrator.wave_dispatch.scope_materialized` record per wave (#1092) —
|
|
516
|
+
* the DEGRADATION half of the chain.
|
|
517
|
+
*
|
|
518
|
+
* It runs here, and not in the hook, precisely because this command runs on
|
|
519
|
+
* EVERY platform while the hook runs on one. Without it a wave with zero
|
|
520
|
+
* `scope_checked` records is ambiguous between "the coordinator skipped the
|
|
521
|
+
* manifest step" (the #1020 failure this whole chain exists for) and "this
|
|
522
|
+
* platform has no dispatch hook".
|
|
523
|
+
*
|
|
524
|
+
* `repoRoot` is the state directory's PARENT, never `process.cwd()`: the state
|
|
525
|
+
* dir is the artefact this command owns, and a caller invoking it from a
|
|
526
|
+
* subdirectory (or a test from the repo root against a `$TMPDIR` state dir)
|
|
527
|
+
* must not write its record into a different repo's ledger.
|
|
528
|
+
*
|
|
529
|
+
* Best-effort in both directions and SILENT on failure: the corpus pins
|
|
530
|
+
* byte-empty stderr on this command's success path
|
|
531
|
+
* (`tests/integration/wave-scope-producer.test.mjs`), and a telemetry write must
|
|
532
|
+
* never be the thing that fails a materialization.
|
|
533
|
+
*
|
|
534
|
+
* @param {{stateDir: string, wave: number, records: Array<{id: string, files: string[]}>}} params
|
|
535
|
+
* @returns {Promise<void>}
|
|
536
|
+
*/
|
|
537
|
+
async function emitScopeMaterialized({ stateDir, wave, records }) {
|
|
538
|
+
try {
|
|
539
|
+
const repoRoot = dirname(resolve(stateDir));
|
|
540
|
+
const agentRecords = records.filter((r) => !isPeerRecordId(r.id));
|
|
541
|
+
const digests = new Set();
|
|
542
|
+
for (const record of agentRecords) {
|
|
543
|
+
if (Array.isArray(record.files) && record.files.length > 0) digests.add(scopeDigest(record.files));
|
|
544
|
+
}
|
|
545
|
+
const { emitEvent, sessionAttribution } = await import('./lib/events.mjs');
|
|
546
|
+
await emitEvent(
|
|
547
|
+
SCOPE_MATERIALIZED_EVENT,
|
|
548
|
+
{
|
|
549
|
+
wave,
|
|
550
|
+
agent_count: agentRecords.length,
|
|
551
|
+
digest_count: digests.size,
|
|
552
|
+
transport_observable: transportObservable(),
|
|
553
|
+
...sessionAttribution(repoRoot),
|
|
554
|
+
},
|
|
555
|
+
{ repoRoot },
|
|
556
|
+
);
|
|
557
|
+
} catch { /* observability is best-effort — it never fails a materialization */ }
|
|
558
|
+
}
|
|
559
|
+
|
|
482
560
|
/**
|
|
483
561
|
* @param {string} message
|
|
484
562
|
* @param {number} code
|
|
@@ -536,6 +614,12 @@ export function main() {
|
|
|
536
614
|
? `${JSON.stringify({ ok: true, aggregatePath, perAgentPaths, removedOrphans, retainedOrphans })}\n`
|
|
537
615
|
: `${aggregatePath}\n`,
|
|
538
616
|
);
|
|
617
|
+
|
|
618
|
+
// Fire-and-forget AFTER stdout, so the telemetry append can never reorder or
|
|
619
|
+
// delay this command's own contract. Node drains the pending append before
|
|
620
|
+
// exiting because nothing here calls `process.exit()`; the `.catch` keeps an
|
|
621
|
+
// unhandled rejection from turning observability into an exit code.
|
|
622
|
+
emitScopeMaterialized({ stateDir: args.stateDir, wave: args.wave, records }).catch(() => {});
|
|
539
623
|
} catch (error) {
|
|
540
624
|
if (error instanceof InputError) {
|
|
541
625
|
fail(error.message, 1);
|
|
@@ -549,7 +633,6 @@ export function main() {
|
|
|
549
633
|
}
|
|
550
634
|
}
|
|
551
635
|
|
|
552
|
-
|
|
553
|
-
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
636
|
+
if (isMainModule(import.meta.url)) {
|
|
554
637
|
main();
|
|
555
638
|
}
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
import { readFileSync, writeFileSync, renameSync, copyFileSync, existsSync } from 'node:fs';
|
|
42
42
|
import { join, dirname, basename } from 'node:path';
|
|
43
43
|
import { validateSession, normalizeSession, ValidationError } from './lib/session-schema.mjs';
|
|
44
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
44
45
|
|
|
45
46
|
const DEFAULT_FILE = '.orchestrator/metrics/sessions.jsonl';
|
|
46
47
|
|
|
@@ -440,7 +441,7 @@ async function main() {
|
|
|
440
441
|
|
|
441
442
|
// Run main() only when this file is invoked directly as a CLI, not when imported.
|
|
442
443
|
// Prevents process.exit during test-time imports (#368).
|
|
443
|
-
if (import.meta.url
|
|
444
|
+
if (isMainModule(import.meta.url)) {
|
|
444
445
|
main().catch((err) => {
|
|
445
446
|
process.stderr.write(`migrate-sessions-jsonl: unexpected error: ${err?.stack ?? err}\n`);
|
|
446
447
|
process.exit(2);
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
|
|
51
51
|
import { promises as fs, existsSync } from 'node:fs';
|
|
52
52
|
import { spawnSync } from 'node:child_process';
|
|
53
|
-
import { pathToFileURL } from 'node:url';
|
|
54
53
|
import path from 'node:path';
|
|
55
54
|
import os from 'node:os';
|
|
56
55
|
import {
|
|
@@ -58,6 +57,7 @@ import {
|
|
|
58
57
|
VAULT_MIGRATION_RULES_PATH,
|
|
59
58
|
} from './lib/vault-migration-rules.mjs';
|
|
60
59
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
60
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
61
61
|
|
|
62
62
|
// ---------------------------------------------------------------------------
|
|
63
63
|
// Constants
|
|
@@ -773,8 +773,7 @@ function emit(opts, rec) {
|
|
|
773
773
|
// importing the module is never a hard error regardless of how the importer runs.
|
|
774
774
|
// ---------------------------------------------------------------------------
|
|
775
775
|
|
|
776
|
-
const invokedDirectly =
|
|
777
|
-
process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
776
|
+
const invokedDirectly =isMainModule(import.meta.url);
|
|
778
777
|
|
|
779
778
|
if (invokedDirectly) {
|
|
780
779
|
main().catch((err) => {
|
package/scripts/release.mjs
CHANGED
|
@@ -356,7 +356,7 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
|
|
|
356
356
|
'templates/python-uv/tests/test_sanity.py',
|
|
357
357
|
]);
|
|
358
358
|
|
|
359
|
-
// `
|
|
359
|
+
// `skills/release/SKILL.md` quotes the `npm view` OUTPUT that proves the 3.18.0 gap,
|
|
360
360
|
// dated at the line. Bumping it would destroy the evidence it exists to carry —
|
|
361
361
|
// the registry state on that date is the whole point of the paragraph.
|
|
362
362
|
//
|
|
@@ -384,7 +384,7 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
|
|
|
384
384
|
// September 10 campaign snapshot: inputs and receipt reproduce the dated 4.3-planned
|
|
385
385
|
// render made while 4.2.0 was current. Exact files only; neighboring marketing
|
|
386
386
|
// remains checked. Do not bump receipt props without a newly reviewed render.
|
|
387
|
-
export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|
|
|
387
|
+
export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|skills\/release\/SKILL\.md|marketing\/remotion\/(?:README\.md|campaign\.json|render-receipt\.json|src\/ReleaseFilm\.tsx)$)/;
|
|
388
388
|
|
|
389
389
|
/** Pure check over packed-entry lines. Returns violations: {name, line}[]. */
|
|
390
390
|
export function checkLeakage(lines) {
|
|
@@ -511,6 +511,49 @@ const LOCKFILE_BASENAMES = new Set(['package-lock.json', 'npm-shrinkwrap.json'])
|
|
|
511
511
|
/** Code files in which a `//`, `*`, `/*` or `#` line is comment prose, never a version surface. */
|
|
512
512
|
const CODE_COMMENT_EXTENSIONS = new Set(['.mjs', '.js', '.cjs', '.ts', '.sh']);
|
|
513
513
|
|
|
514
|
+
/** Workflow/CI files in which a SHA-pinned `uses:` carries a third-party version as a trailing comment. */
|
|
515
|
+
const YAML_EXTENSIONS = new Set(['.yml', '.yaml']);
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Is this YAML row a SHA-pinned THIRD-PARTY action whose version lives only in the
|
|
519
|
+
* trailing comment?
|
|
520
|
+
*
|
|
521
|
+
* Measured on the 5.1.0 cut (2026-09-13): `.github/workflows/test.yml` carries
|
|
522
|
+
* `uses: actions/setup-node@a0853c2… # v5.0.0` twice. That `5.0.0` is setup-node's
|
|
523
|
+
* version, not ours — the sweep matched it only because our previous release happened
|
|
524
|
+
* to land on the same number, so the false positive is triggered by COINCIDENCE and
|
|
525
|
+
* would reappear for any action whose pin equals our next version.
|
|
526
|
+
*
|
|
527
|
+
* Expressed as a PREDICATE, like the three classes above, for the reason
|
|
528
|
+
* `.claude/rules/measurement-discipline.md` records: a per-path allowlist row fixes
|
|
529
|
+
* this file and leaves the class open for the next workflow.
|
|
530
|
+
*
|
|
531
|
+
* Two conditions, both required, so the predicate cannot mask a stale surface of ours:
|
|
532
|
+
* 1. the line pins a reference with `uses:` BEFORE the comment marker, and
|
|
533
|
+
* 2. EVERY occurrence of the literal sits AFTER that marker.
|
|
534
|
+
* A version in the pin itself (`uses: foo@v5.0.0`) fails condition 2 and is still swept.
|
|
535
|
+
*
|
|
536
|
+
* NAMED CEILING (BV-004): no SURFACES pattern targets a `.yml`/`.yaml` file today, so a
|
|
537
|
+
* YAML hit is never our own surface anyway. Revisit trigger: the first version surface
|
|
538
|
+
* added to a YAML file — then this predicate must also exclude that file's pattern.
|
|
539
|
+
*
|
|
540
|
+
* @param {string} content — the matching line's text
|
|
541
|
+
* @param {string} literal — the previous release literal being swept for
|
|
542
|
+
* @returns {boolean} true = third-party pin comment, skip the row
|
|
543
|
+
*/
|
|
544
|
+
export function isPinnedActionComment(content, literal) {
|
|
545
|
+
const hash = content.indexOf('#');
|
|
546
|
+
if (hash === -1) return false;
|
|
547
|
+
if (!/\buses:\s*\S/.test(content.slice(0, hash))) return false;
|
|
548
|
+
const re = versionTokenRegex(literal);
|
|
549
|
+
let seen = 0;
|
|
550
|
+
for (let m = re.exec(content); m; m = re.exec(content)) {
|
|
551
|
+
seen += 1;
|
|
552
|
+
if (m.index < hash) return false;
|
|
553
|
+
}
|
|
554
|
+
return seen > 0;
|
|
555
|
+
}
|
|
556
|
+
|
|
514
557
|
/**
|
|
515
558
|
* Is this `path:line:content` row version HISTORY rather than a stale surface?
|
|
516
559
|
*
|
|
@@ -546,6 +589,7 @@ function isHistoryRow(file, line, content, prevTag) {
|
|
|
546
589
|
}
|
|
547
590
|
const dot = base.lastIndexOf('.');
|
|
548
591
|
const ext = dot === -1 ? '' : base.slice(dot);
|
|
592
|
+
if (YAML_EXTENSIONS.has(ext) && isPinnedActionComment(content, prevTag)) return true;
|
|
549
593
|
if (CODE_COMMENT_EXTENSIONS.has(ext)) {
|
|
550
594
|
const trimmed = content.trim();
|
|
551
595
|
if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) return true;
|
|
@@ -864,6 +908,11 @@ export function evaluateCiRow(ci) {
|
|
|
864
908
|
* "we could not read the mirror" is not "the mirror is green" (the fail-closed
|
|
865
909
|
* house rule at the top of this file).
|
|
866
910
|
*
|
|
911
|
+
* This function only judges the `ci` reading it is handed — it is
|
|
912
|
+
* `evaluateCiPreflightRows` (the caller, see its own docblock) that fetches
|
|
913
|
+
* `ci` for the commit actually being released, rather than whichever HEAD the
|
|
914
|
+
* GitHub mirror itself reports.
|
|
915
|
+
*
|
|
867
916
|
* @param {string|undefined} repoSpec — `resolveRepoSpec({vcs:'github'})`, undefined when no github remote resolves
|
|
868
917
|
* @param {null | {status?: string, failingJobName?: string, degraded?: string}} ci
|
|
869
918
|
* @returns {{ok: boolean, detail: string}}
|
|
@@ -925,6 +974,62 @@ function readPackageVersion(repoRoot) {
|
|
|
925
974
|
return JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')).version;
|
|
926
975
|
}
|
|
927
976
|
|
|
977
|
+
/**
|
|
978
|
+
* Both CI preflight rows (`ci-green-on-head` + `ci-green-on-head-github`) for
|
|
979
|
+
* ONE commit — the commit actually about to be released — never whichever
|
|
980
|
+
* HEAD the queried platform happens to report.
|
|
981
|
+
*
|
|
982
|
+
* Before this function existed, neither `checkCiStatus` call below passed
|
|
983
|
+
* `sha`, so the GitHub branch fell back to `commits/HEAD` — the MIRROR's own
|
|
984
|
+
* default-branch head, which is the release commit only once `head-pushed-github`
|
|
985
|
+
* has ALREADY proven `github/main == local HEAD`. A release cut before that
|
|
986
|
+
* push landed asked GitHub about a commit that was never pushed and could read
|
|
987
|
+
* green for work GitHub has not seen at all (measured 2026-09-16 for
|
|
988
|
+
* `3ebf0e9d`). The GitLab branch was already correct by default (`deps.sha ??
|
|
989
|
+
* getHeadSha(repoRoot)`), so only the two call sites below needed the fix —
|
|
990
|
+
* see `checkCiStatus`'s own docblock in `ci-status-banner.mjs` for the `sha`
|
|
991
|
+
* contract (#1332).
|
|
992
|
+
*
|
|
993
|
+
* Factored out of `preflight()` as its own async, DI-testable unit:
|
|
994
|
+
* `checkCiStatus` reaches the network by default, and `preflight()`'s
|
|
995
|
+
* surrounding checks (git, npm) are not test-doubled, so this is the seam
|
|
996
|
+
* through which the `sha` wiring can be unit-tested without spinning up a
|
|
997
|
+
* full fixture release.
|
|
998
|
+
*
|
|
999
|
+
* @param {string} repoRoot
|
|
1000
|
+
* @param {string} head - full hex commit id of the commit being released (the
|
|
1001
|
+
* local HEAD `preflight()` already computed for the remote-parity rows).
|
|
1002
|
+
* @param {{
|
|
1003
|
+
* skipCi?: boolean,
|
|
1004
|
+
* checkCiStatus?: Function,
|
|
1005
|
+
* resolveRepoSpec?: (opts: { repoRoot: string, vcs: 'github' }) => string|undefined,
|
|
1006
|
+
* }} [deps]
|
|
1007
|
+
* @returns {Promise<{ gitlab: {ok: boolean, detail: string}, github: {ok: boolean, detail: string} }>}
|
|
1008
|
+
*/
|
|
1009
|
+
export async function evaluateCiPreflightRows(repoRoot, head, {
|
|
1010
|
+
skipCi = false,
|
|
1011
|
+
checkCiStatus: checkCiStatusDep,
|
|
1012
|
+
resolveRepoSpec: resolveRepoSpecDep = resolveRepoSpec,
|
|
1013
|
+
} = {}) {
|
|
1014
|
+
if (skipCi) {
|
|
1015
|
+
const skipped = { ok: true, detail: 'SKIPPED via --skip-ci' };
|
|
1016
|
+
return { gitlab: skipped, github: skipped };
|
|
1017
|
+
}
|
|
1018
|
+
const checkCiStatusImpl = checkCiStatusDep ?? (await import('./lib/ci-status-banner.mjs')).checkCiStatus;
|
|
1019
|
+
|
|
1020
|
+
const ci = await checkCiStatusImpl({ repoRoot, timeoutMs: 15000, sha: head });
|
|
1021
|
+
const gitlab = evaluateCiRow(ci);
|
|
1022
|
+
|
|
1023
|
+
const githubSpec = resolveRepoSpecDep({ repoRoot, vcs: 'github' });
|
|
1024
|
+
let githubCi = null;
|
|
1025
|
+
if (githubSpec) {
|
|
1026
|
+
githubCi = await checkCiStatusImpl({ repoRoot, vcs: 'github', timeoutMs: 15000, sha: head });
|
|
1027
|
+
}
|
|
1028
|
+
const github = evaluateGithubCiRow(githubSpec, githubCi);
|
|
1029
|
+
|
|
1030
|
+
return { gitlab, github };
|
|
1031
|
+
}
|
|
1032
|
+
|
|
928
1033
|
async function preflight(repoRoot, target, { skipCi = false } = {}) {
|
|
929
1034
|
const checks = [];
|
|
930
1035
|
const add = (name, ok, detail = '') => checks.push({ name, ok, detail });
|
|
@@ -1054,39 +1159,23 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
|
|
|
1054
1159
|
add('npm-token-live', false, err.message);
|
|
1055
1160
|
}
|
|
1056
1161
|
|
|
1057
|
-
// 6. CI green on HEAD
|
|
1058
|
-
//
|
|
1059
|
-
//
|
|
1060
|
-
//
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
//
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
// detection order. The GitHub check-runs path reads `commits/HEAD`, i.e. the
|
|
1075
|
-
// mirror's default-branch head — which is HEAD only because `head-pushed-github`
|
|
1076
|
-
// above proves github/main == local HEAD; that row is this one's precondition,
|
|
1077
|
-
// not a duplicate of it.
|
|
1078
|
-
if (skipCi) {
|
|
1079
|
-
add('ci-green-on-head-github', true, 'SKIPPED via --skip-ci');
|
|
1080
|
-
} else {
|
|
1081
|
-
const githubSpec = resolveRepoSpec({ repoRoot, vcs: 'github' });
|
|
1082
|
-
let githubCi = null;
|
|
1083
|
-
if (githubSpec) {
|
|
1084
|
-
const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
|
|
1085
|
-
githubCi = await checkCiStatus({ repoRoot, vcs: 'github', timeoutMs: 15000 });
|
|
1086
|
-
}
|
|
1087
|
-
const row = evaluateGithubCiRow(githubSpec, githubCi);
|
|
1088
|
-
add('ci-green-on-head-github', row.ok, row.detail);
|
|
1089
|
-
}
|
|
1162
|
+
// 6/6b. CI green on HEAD, both platforms — judged for the commit actually
|
|
1163
|
+
// being released, never whichever HEAD the queried platform reports (see
|
|
1164
|
+
// evaluateCiPreflightRows's docblock for the `sha`-wiring history, #1332).
|
|
1165
|
+
//
|
|
1166
|
+
// GitLab: the repo's iron session-start rule applies to releases doubly —
|
|
1167
|
+
// local green is not evidence — see .claude/rules. GitHub: this second row
|
|
1168
|
+
// exists because `detectVcsFamily` picks `origin` (GitLab) for the row
|
|
1169
|
+
// above, whose pipeline is Linux-only; the macOS matrix leg lives
|
|
1170
|
+
// exclusively in `.github/workflows/test.yml`. `vcs: 'github'` forces the
|
|
1171
|
+
// probe onto the mirror without touching the detection order.
|
|
1172
|
+
// SELF-DISABLING: a checkout with no `github` remote has no mirror to be
|
|
1173
|
+
// red, so `evaluateGithubCiRow` reports `skipped` rather than red — see its
|
|
1174
|
+
// own docblock. --skip-ci is refused under --publish upstream in
|
|
1175
|
+
// validateFlags(); it can only reach this branch from --check.
|
|
1176
|
+
const ciRows = await evaluateCiPreflightRows(repoRoot, head, { skipCi });
|
|
1177
|
+
add('ci-green-on-head', ciRows.gitlab.ok, ciRows.gitlab.detail);
|
|
1178
|
+
add('ci-green-on-head-github', ciRows.github.ok, ciRows.github.detail);
|
|
1090
1179
|
|
|
1091
1180
|
// 7. Leakage gate over the actual pack file list.
|
|
1092
1181
|
// `npm_config_loglevel` is INHERITED, and `npm pack --dry-run` writes its whole
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
|
|
41
41
|
import { promises as fs, existsSync } from 'node:fs';
|
|
42
42
|
import { spawnSync } from 'node:child_process';
|
|
43
|
-
import { pathToFileURL } from 'node:url';
|
|
44
43
|
import path from 'node:path';
|
|
45
44
|
import {
|
|
46
45
|
loadVaultRelocationRules,
|
|
@@ -55,6 +54,7 @@ import {
|
|
|
55
54
|
} from './lib/vault-relocation-rules.mjs';
|
|
56
55
|
import { buildBackfillIndex, parseSessionId } from './lib/vault-repo-backfill.mjs';
|
|
57
56
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
57
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
58
58
|
|
|
59
59
|
// ---------------------------------------------------------------------------
|
|
60
60
|
// Constants
|
|
@@ -1158,8 +1158,7 @@ async function main() {
|
|
|
1158
1158
|
// Entry guard (run only when invoked directly)
|
|
1159
1159
|
// ---------------------------------------------------------------------------
|
|
1160
1160
|
|
|
1161
|
-
const invokedDirectly =
|
|
1162
|
-
process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
1161
|
+
const invokedDirectly =isMainModule(import.meta.url);
|
|
1163
1162
|
|
|
1164
1163
|
if (invokedDirectly) {
|
|
1165
1164
|
main().catch((err) => {
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
import fs from 'node:fs';
|
|
38
38
|
import { parseArgs } from 'node:util';
|
|
39
39
|
import path from 'node:path';
|
|
40
|
-
import { fileURLToPath } from 'node:url';
|
|
41
40
|
|
|
42
41
|
import { repairLedger, CANONICAL_LEDGER_REL } from './lib/session-record-repair.mjs';
|
|
43
42
|
import { getProjectDir } from './lib/platform.mjs';
|
|
43
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
44
44
|
|
|
45
45
|
const USAGE =
|
|
46
46
|
'Usage: node scripts/repair-invalid-sessions.mjs [--dry-run|--apply] [--json]\n' +
|
|
@@ -200,7 +200,7 @@ async function main() {
|
|
|
200
200
|
process.exit(summary.ok === false ? 3 : 0);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
const isDirectRun =
|
|
203
|
+
const isDirectRun =isMainModule(import.meta.url);
|
|
204
204
|
if (isDirectRun) {
|
|
205
205
|
main().catch((err) => {
|
|
206
206
|
process.stderr.write(`repair-invalid-sessions: unexpected error: ${err?.stack ?? err}\n`);
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
43
43
|
import { join, resolve } from 'node:path';
|
|
44
44
|
import { parseArgs } from 'node:util';
|
|
45
|
-
import { fileURLToPath } from 'node:url';
|
|
46
45
|
import { resolveInstructionFile } from './lib/common.mjs';
|
|
47
46
|
import { parseSessionConfig } from './lib/config.mjs';
|
|
48
47
|
import { resolveAndRecordSessionShape } from './lib/session-shape.mjs';
|
|
48
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
49
49
|
|
|
50
50
|
const USAGE = [
|
|
51
51
|
'Usage: node scripts/session-shape.mjs --repo-root <path> --session-type <housekeeping|feature|deep>',
|
|
@@ -258,7 +258,7 @@ async function main() {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
// Entrypoint guard — importing this file must not run it (check-unwired-features S3).
|
|
261
|
-
if (
|
|
261
|
+
if (isMainModule(import.meta.url)) {
|
|
262
262
|
main().catch((err) => {
|
|
263
263
|
process.stderr.write(`session-shape: unexpected error: ${err?.stack ?? err}\n`);
|
|
264
264
|
process.exit(EXIT_CONFIG_IO);
|
package/scripts/site-numbers.mjs
CHANGED
|
@@ -101,10 +101,11 @@
|
|
|
101
101
|
import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
102
102
|
import { join, resolve, relative } from 'node:path';
|
|
103
103
|
import { execFileSync } from 'node:child_process';
|
|
104
|
-
import { fileURLToPath } from 'node:url';
|
|
105
104
|
|
|
106
105
|
import { writeStdoutLineSync, writeJsonAtomicSync } from './lib/io.mjs';
|
|
107
106
|
import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
|
|
107
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
108
|
+
import { slashCommandNames } from './lib/user-invocable-skills.mjs';
|
|
108
109
|
|
|
109
110
|
/** Machine-readable schema tag for the --json envelope. */
|
|
110
111
|
export const SCHEMA = 'site-numbers/1';
|
|
@@ -169,11 +170,37 @@ export function countSkills(root) {
|
|
|
169
170
|
return n;
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
/**
|
|
173
|
+
/**
|
|
174
|
+
* The product's "slash commands" number: `commands/*.md` ∪ skills carrying an
|
|
175
|
+
* EXPLICIT `user-invocable: true`, deduplicated by name.
|
|
176
|
+
*
|
|
177
|
+
* NOT `ls commands/*.md | wc -l` any more, and the change is not cosmetic. After
|
|
178
|
+
* the 2026-09-16 fold (#1370) `commands/` holds two files — `session` and
|
|
179
|
+
* `templates-ack`, the only two names a skill cannot carry — while 26 operator-
|
|
180
|
+
* facing slash commands exist. A command file only ever resolved because a
|
|
181
|
+
* same-named skill existed, and the twin additionally listed the entry twice in
|
|
182
|
+
* the `/` picker; the skill frontmatter flag is what makes a slash command a
|
|
183
|
+
* slash command now. Counting the directory would have reported 2.
|
|
184
|
+
*
|
|
185
|
+
* Dedup by name is load-bearing: a name present on BOTH sides is the picker-
|
|
186
|
+
* duplicate defect (`tests/commands/headless-bare-command-availability.test.mjs`
|
|
187
|
+
* fails on it), so it must count once here rather than inflate the tile.
|
|
188
|
+
*
|
|
189
|
+
* null (→ the tile renders as unmeasured) only when NEITHER `commands/` nor
|
|
190
|
+
* `skills/` exists — i.e. this is not a plugin tree at all. A tree with one of
|
|
191
|
+
* the two counts what it has; a missing `commands/` is now the NORMAL state, not
|
|
192
|
+
* a missing measurement.
|
|
193
|
+
*
|
|
194
|
+
* NO divergence from `skills/claude-md-drift-check/checker.mjs` `command-count`:
|
|
195
|
+
* that checker unions the same user-invocable skills (`checker.mjs:435-446`,
|
|
196
|
+
* measured 2026-09-17), and both routes reach the same verdict through the same
|
|
197
|
+
* SSOT — `isUserInvocableValue` in `scripts/lib/user-invocable-skills.mjs`. The
|
|
198
|
+
* checker stays GENERIC over consumer repos only in the sense that a repo
|
|
199
|
+
* without `skills/` sees the union collapse to the directory count on its own.
|
|
200
|
+
*/
|
|
173
201
|
export function countCommands(root) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return readdirSync(dir).filter((f) => f.endsWith('.md') && !f.startsWith('.')).length;
|
|
202
|
+
if (!isDir(join(root, 'commands')) && !isDir(join(root, 'skills'))) return null;
|
|
203
|
+
return slashCommandNames(root).length;
|
|
177
204
|
}
|
|
178
205
|
|
|
179
206
|
/**
|
|
@@ -400,7 +427,8 @@ export const METRIC_DEFS = Object.freeze([
|
|
|
400
427
|
{
|
|
401
428
|
id: 'commands',
|
|
402
429
|
provenance: false,
|
|
403
|
-
source:
|
|
430
|
+
source:
|
|
431
|
+
'ls commands/*.md | wc -l + grep -l "^user-invocable: true" skills/*/SKILL.md | wc -l (union by name — see countCommands)',
|
|
404
432
|
compute: (root) => fmtCount(countCommands(root)),
|
|
405
433
|
},
|
|
406
434
|
{
|
|
@@ -1398,11 +1426,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
1398
1426
|
return exitCode;
|
|
1399
1427
|
}
|
|
1400
1428
|
|
|
1401
|
-
const isMain =
|
|
1402
|
-
typeof process !== 'undefined' &&
|
|
1403
|
-
process.argv[1] !== null &&
|
|
1404
|
-
process.argv[1] !== undefined &&
|
|
1405
|
-
resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
|
|
1429
|
+
const isMain =isMainModule(import.meta.url);
|
|
1406
1430
|
|
|
1407
1431
|
if (isMain) {
|
|
1408
1432
|
const argv = process.argv.slice(2);
|