session-orchestrator 5.12.0 → 5.13.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +4 -1
- package/.codex-plugin/agents/explorer.toml +1 -1
- package/.codex-plugin/agents/session-reviewer.toml +2 -2
- package/.codex-plugin/agents/wave-worker.toml +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +8 -0
- package/.orchestrator/policy/public-export.json +30 -0
- package/CHANGELOG.md +162 -4
- package/README.md +126 -127
- package/agents/analyst.md +7 -2
- package/agents/architect-reviewer.md +7 -2
- package/agents/code-implementer.md +4 -2
- package/agents/db-specialist.md +9 -2
- package/agents/dialectic-deriver.md +9 -3
- package/agents/docs-writer.md +7 -2
- package/agents/eval-judge.md +7 -1
- package/agents/qa-strategist.md +7 -2
- package/agents/security-reviewer.md +9 -2
- package/agents/session-reviewer.md +18 -2
- package/agents/skill-applied-judge.md +7 -1
- package/agents/test-writer.md +9 -2
- package/agents/ui-developer.md +9 -2
- package/agents/ux-evaluator.md +6 -1
- package/commands/session.md +1 -1
- package/commands/templates-ack.md +89 -18
- package/docs/agent-authoring.md +30 -11
- package/docs/ci-setup.md +38 -2
- package/docs/codex-setup.md +33 -9
- package/docs/components.md +13 -2
- package/docs/cursor-setup.md +3 -3
- package/docs/events-schema.md +5 -4
- package/docs/evidence-candidates.md +63 -0
- package/docs/fleet-checkin-audit.md +7 -4
- package/docs/github-mirror-protection.md +150 -3
- package/docs/install.md +90 -17
- package/docs/instruction-delivery.md +1 -1
- package/docs/migration-v4.md +2 -2
- package/docs/mods.md +118 -0
- package/docs/owner-config-schema.md +1 -0
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/pm-skills-marketplace.md +4 -3
- package/docs/rule-authoring.md +24 -13
- package/docs/scope-collision-guard.md +2 -1
- package/docs/session-config-reference.md +49 -15
- package/docs/session-config-template.md +30 -22
- package/docs/sessions-ledger-repair.md +137 -0
- package/docs/vault-docs-architecture.md +1 -1
- package/hooks/_lib/hook-import-set.json +406 -73
- package/hooks/_lib/hook-root.mjs +301 -0
- package/hooks/_lib/lock-bootstrap.mjs +434 -4
- package/hooks/_lib/orchestrator-ignore.mjs +3 -0
- package/hooks/_lib/subagent-transcript.mjs +2 -2
- package/hooks/_lib/vcs-create-matcher.mjs +13 -17
- package/hooks/cwd-change-restore.mjs +5 -1
- package/hooks/enforce-commands.mjs +4 -3
- package/hooks/enforce-scope.mjs +11 -1
- package/hooks/hooks-codex.json +48 -2
- package/hooks/hooks.json +31 -31
- package/hooks/on-session-end.mjs +38 -15
- package/hooks/on-session-start.mjs +139 -49
- package/hooks/on-stop.mjs +11 -6
- package/hooks/post-bash-issue-budget-refund.mjs +6 -1
- package/hooks/post-bash-write-verify.mjs +8 -4
- package/hooks/post-edit-validate.mjs +45 -52
- package/hooks/post-subagent-discovery-validator.mjs +2 -2
- package/hooks/post-tool-batch-wave-signal.mjs +8 -4
- package/hooks/post-tool-failure-corrective-context.mjs +5 -1
- package/hooks/pre-bash-destructive-guard.mjs +220 -8
- package/hooks/pre-bash-issue-budget.mjs +6 -1
- package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
- package/hooks/pre-bash-staging-fence.mjs +258 -14
- package/hooks/pre-bash-templates-first.mjs +28 -27
- package/hooks/run-node.sh +1 -1
- package/hooks/skill-invocation-telemetry.mjs +15 -8
- package/hooks/subagent-telemetry.mjs +77 -14
- package/hooks/wave-scope-commit-guard.mjs +143 -14
- package/mods/so-hud/.claude-plugin/plugin.json +11 -0
- package/mods/so-hud/README.md +57 -0
- package/mods/so-hud/hooks/beacon-io.mjs +101 -0
- package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
- package/mods/so-hud/hooks/hooks.json +1 -0
- package/mods/so-hud/hooks/logic.ts +182 -0
- package/mods/so-hud/hooks/register.tsx +670 -0
- package/mods/so-hud/hooks/rules.ts +2998 -0
- package/mods/so-hud/tsconfig.json +8 -0
- package/mods/so-hud/types/index.d.ts +36 -0
- package/package.json +4 -2
- package/rules/_index.md +1 -1
- package/rules/always-on/ask-via-tool.md +1 -1
- package/rules/always-on/loop-and-monitor.md +17 -194
- package/rules/always-on/parallel-sessions.md +3 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +1 -1
- package/rules/opt-in-stack/security-web.md +5 -1
- package/scripts/agent-usage-census.mjs +79 -0
- package/scripts/apply-session-learnings.mjs +34 -6
- package/scripts/archive-closed-prds.mjs +21 -5
- package/scripts/autopilot.mjs +11 -5
- package/scripts/backfill-abandoned-sessions.mjs +13 -3
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +61 -6
- package/scripts/backfill-sessions.mjs +16 -2
- package/scripts/check-package-manager.mjs +26 -8
- package/scripts/check-sessions-integrity.mjs +4 -1
- package/scripts/ci/assert-vitest-green.mjs +3 -0
- package/scripts/ci/windows-smoke.mjs +201 -0
- package/scripts/close-precheck.mjs +55 -0
- package/scripts/dialectic-deriver.mjs +20 -12
- package/scripts/emit-event.mjs +2 -2
- package/scripts/emit-session.mjs +239 -20
- package/scripts/eval-session.mjs +17 -3
- package/scripts/export-hw-learnings.mjs +29 -2
- package/scripts/fleet-checkin-audit.mjs +8 -4
- package/scripts/fleet-checkin.mjs +39 -27
- package/scripts/gc-stale-worktrees.mjs +6 -1
- package/scripts/github-export.mjs +128 -0
- package/scripts/github-protection-audit.mjs +3 -1
- package/scripts/handover-queue-check.mjs +209 -0
- package/scripts/harness-audit.mjs +6 -4
- package/scripts/lib/agent-frontmatter.mjs +57 -10
- package/scripts/lib/agent-status.mjs +125 -7
- package/scripts/lib/agent-usage-census.mjs +394 -0
- package/scripts/lib/auto-dialectic.mjs +17 -10
- package/scripts/lib/auto-dream.mjs +22 -14
- package/scripts/lib/autopilot/loop.mjs +3 -1
- package/scripts/lib/autopilot/recent-runs.mjs +4 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
- package/scripts/lib/autopilot/telemetry.mjs +5 -1
- package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
- package/scripts/lib/baseline-archetypes.mjs +10 -2
- package/scripts/lib/blocked-commands-policy.mjs +4 -3
- package/scripts/lib/build-live-signals.mjs +13 -9
- package/scripts/lib/ci-status-banner.mjs +329 -24
- package/scripts/lib/codex/plugin-contract.mjs +8 -0
- package/scripts/lib/cold-start-detector.mjs +5 -2
- package/scripts/lib/command-blocker.mjs +603 -43
- package/scripts/lib/common.mjs +3 -2
- package/scripts/lib/config/cross-repo.mjs +74 -7
- package/scripts/lib/config/host-paths.mjs +6 -2
- package/scripts/lib/config/section-extractor.mjs +65 -11
- package/scripts/lib/config/template-keys.mjs +153 -0
- package/scripts/lib/config/vault-integration.mjs +1 -0
- package/scripts/lib/config.mjs +2 -2
- package/scripts/lib/coordinator-snapshot.mjs +1 -1
- package/scripts/lib/discovery/triage-state.mjs +2 -1
- package/scripts/lib/dispatcher/rank.mjs +5 -7
- package/scripts/lib/ecosystem-health.mjs +2 -2
- package/scripts/lib/eval/engine.mjs +3 -2
- package/scripts/lib/eval/session-resolve.mjs +6 -2
- package/scripts/lib/eval/sink.mjs +4 -3
- package/scripts/lib/events-retention-banner.mjs +3 -2
- package/scripts/lib/events-schema.mjs +10 -0
- package/scripts/lib/events.mjs +23 -12
- package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
- package/scripts/lib/fleet-protocol.mjs +4 -0
- package/scripts/lib/gates/gate-full.mjs +172 -11
- package/scripts/lib/gates/gate-helpers.mjs +236 -23
- package/scripts/lib/git-config-drift.mjs +141 -7
- package/scripts/lib/github-export.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
- package/scripts/lib/handover-queue-marks.mjs +159 -0
- package/scripts/lib/hardware-pattern-detector.mjs +103 -13
- package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
- package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
- package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
- package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
- package/scripts/lib/host-identity.mjs +9 -5
- package/scripts/lib/http-request.mjs +64 -0
- package/scripts/lib/instruction-budget-guard.mjs +30 -22
- package/scripts/lib/io.mjs +6 -3
- package/scripts/lib/issue-budget-reconcile.mjs +3 -2
- package/scripts/lib/issue-budget.mjs +36 -7
- package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
- package/scripts/lib/learnings/io.mjs +289 -15
- package/scripts/lib/learnings/select.mjs +53 -4
- package/scripts/lib/learnings/store-role.mjs +209 -0
- package/scripts/lib/learnings/surface.mjs +46 -7
- package/scripts/lib/ledger-path.mjs +113 -0
- package/scripts/lib/maintenance-due-banner.mjs +63 -12
- package/scripts/lib/memory-banner.mjs +5 -6
- package/scripts/lib/memory-proposals/collector.mjs +5 -2
- package/scripts/lib/memory-proposals/sink.mjs +7 -4
- package/scripts/lib/memory-proposals/store.mjs +22 -5
- package/scripts/lib/mode-selector-accuracy.mjs +2 -1
- package/scripts/lib/no-follow-open.mjs +57 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
- package/scripts/lib/own-lock-root.mjs +95 -0
- package/scripts/lib/owner-interview.mjs +2 -1
- package/scripts/lib/owner-yaml.mjs +70 -7
- package/scripts/lib/platform.mjs +161 -9
- package/scripts/lib/plugin-update-banner.mjs +15 -11
- package/scripts/lib/pre-push-local-partial.mjs +117 -0
- package/scripts/lib/pre-push-remote-gate.mjs +151 -25
- package/scripts/lib/process-group.mjs +270 -34
- package/scripts/lib/project-hygiene.mjs +28 -0
- package/scripts/lib/quality-gate.mjs +33 -9
- package/scripts/lib/reconcile/backlog.mjs +29 -15
- package/scripts/lib/reconcile/engine.mjs +26 -2
- package/scripts/lib/reconcile/idempotency.mjs +65 -3
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
- package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
- package/scripts/lib/resource-probe/evaluate.mjs +32 -9
- package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
- package/scripts/lib/rule-loader.mjs +20 -10
- package/scripts/lib/scope-baseline.mjs +123 -8
- package/scripts/lib/scope-echo.mjs +5 -2
- package/scripts/lib/scope-gate.mjs +55 -20
- package/scripts/lib/session-close-backfill.mjs +75 -7
- package/scripts/lib/session-end/close-precheck.mjs +151 -0
- package/scripts/lib/session-end/phase-skip.mjs +8 -7
- package/scripts/lib/session-end/tail-runner.mjs +7 -2
- package/scripts/lib/session-id.mjs +9 -1
- package/scripts/lib/session-identity/own-session.mjs +63 -0
- package/scripts/lib/session-lock.mjs +114 -0
- package/scripts/lib/session-record-repair.mjs +245 -35
- package/scripts/lib/session-registry.mjs +72 -9
- package/scripts/lib/session-schema/constants.mjs +9 -0
- package/scripts/lib/session-schema/filters.mjs +56 -1
- package/scripts/lib/session-schema/validator.mjs +15 -5
- package/scripts/lib/session-start-probes.mjs +149 -22
- package/scripts/lib/session-token-rollup.mjs +2 -1
- package/scripts/lib/session-work-trees.mjs +166 -0
- package/scripts/lib/sessions-canonical.mjs +61 -3
- package/scripts/lib/sessions-integrity-banner.mjs +17 -39
- package/scripts/lib/sessions-staleness-banner.mjs +8 -5
- package/scripts/lib/sh-quote.mjs +1 -0
- package/scripts/lib/skill-evidence-window.mjs +5 -1
- package/scripts/lib/skill-health/join.mjs +10 -9
- package/scripts/lib/skill-invocations-schema.mjs +13 -0
- package/scripts/lib/skill-judgments-schema.mjs +6 -2
- package/scripts/lib/snapshot-probe.mjs +414 -0
- package/scripts/lib/so-hud/collect.mjs +49 -5
- package/scripts/lib/so-hud/runtime.mjs +15 -4
- package/scripts/lib/spiral-carryover.mjs +8 -1
- package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
- package/scripts/lib/state-md/idle-reset.mjs +14 -5
- package/scripts/lib/state-md/mission-status.mjs +5 -1
- package/scripts/lib/state-md-peer-guard.mjs +2 -0
- package/scripts/lib/state-md.mjs +31 -8
- package/scripts/lib/sunset/walker.mjs +4 -3
- package/scripts/lib/telemetry/consent.mjs +4 -2
- package/scripts/lib/telemetry/queue.mjs +3 -3
- package/scripts/lib/telemetry/sync.mjs +45 -9
- package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
- package/scripts/lib/tmux-layout/layouts.mjs +20 -3
- package/scripts/lib/ux-grill/paths.mjs +5 -1
- package/scripts/lib/validate/check-agents.mjs +37 -6
- package/scripts/lib/validate/check-component-paths.mjs +25 -1
- package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
- package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
- package/scripts/lib/validate/check-json-files.mjs +3 -1
- package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
- package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
- package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
- package/scripts/lib/validate/check-unwired-features.mjs +4 -2
- package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
- package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
- package/scripts/lib/validate/confidential-names.mjs +3 -1
- package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
- package/scripts/lib/vault-mirror/namespace.mjs +19 -7
- package/scripts/lib/vault-mirror/process.mjs +56 -27
- package/scripts/lib/vault-mirror/prune.mjs +5 -2
- package/scripts/lib/vault-mirror/render-learnings.mjs +3 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
- package/scripts/lib/vault-mirror/retention.mjs +2 -2
- package/scripts/lib/vault-mirror/utils.mjs +79 -0
- package/scripts/lib/vault-staleness-banner.mjs +4 -7
- package/scripts/lib/vault-status/board-writer.mjs +2 -1
- package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
- package/scripts/lib/win-process-tree.mjs +137 -0
- package/scripts/lib/workspace.mjs +1 -1
- package/scripts/lib/worktree/constants.mjs +1 -1
- package/scripts/lib/worktree/lifecycle.mjs +1 -1
- package/scripts/lib/worktree/listing.mjs +1 -1
- package/scripts/lib/zx.mjs +50 -0
- package/scripts/mcp-server.mjs +16 -7
- package/scripts/mcp-server.sh +13 -83
- package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
- package/scripts/memory-propose.mjs +10 -3
- package/scripts/migrate-cold-start-seed.mjs +7 -0
- package/scripts/migrate-legacy-learnings.sh +22 -0
- package/scripts/migrate-sessions-jsonl.mjs +16 -2
- package/scripts/migrate-vault-paths.mjs +29 -13
- package/scripts/parse-config.mjs +71 -14
- package/scripts/print-learnings-index.mjs +25 -2
- package/scripts/release-session-lock.mjs +94 -7
- package/scripts/release.mjs +451 -24
- package/scripts/relocate-vault-corpus.mjs +11 -7
- package/scripts/repair-invalid-sessions.mjs +68 -3
- package/scripts/resolve-session-invocation.mjs +10 -17
- package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
- package/scripts/run-quality-gate.mjs +33 -7
- package/scripts/self-update.mjs +56 -7
- package/scripts/session-commit-files.mjs +133 -0
- package/scripts/site-numbers.mjs +52 -9
- package/scripts/snapshot-probe.mjs +156 -0
- package/scripts/so-hud.mjs +29 -6
- package/scripts/state-md-idle-reset.mjs +2 -1
- package/scripts/sweep-expired-learnings.mjs +54 -6
- package/scripts/sweep-expired-rules.mjs +4 -1
- package/scripts/validate-plugin.mjs +22 -0
- package/scripts/vault-integration-watcher.mjs +54 -6
- package/scripts/vault-mirror.mjs +9 -0
- package/skills/_shared/fleet-protocol.md +142 -1
- package/skills/_shared/instruction-file-resolution.md +1 -1
- package/skills/_shared/monitor-patterns.md +44 -25
- package/skills/_shared/platform-tools.md +7 -0
- package/skills/_shared/state-ownership.md +3 -2
- package/skills/architecture/references/domain-model.md +1 -1
- package/skills/bootstrap/SKILL.md +3 -1
- package/skills/bootstrap/_shared-template.md +4 -4
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +1 -1
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
- package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
- package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
- package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
- package/skills/claude-md-drift-check/SKILL.md +3 -3
- package/skills/claude-md-drift-check/checker.mjs +61 -101
- package/skills/close/SKILL.md +20 -7
- package/skills/discovery/probes/docs-staleness.mjs +7 -5
- package/skills/discovery/probes/frontend-slop.mjs +7 -3
- package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
- package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
- package/skills/discovery/probes/vault-staleness.mjs +5 -3
- package/skills/discovery/probes-session.md +3 -0
- package/skills/discovery/probes-vault.md +2 -0
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/eval/SKILL.md +3 -1
- package/skills/evolve/SKILL.md +2 -0
- package/skills/evolve/references/evolve-analyze-mode.md +3 -3
- package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
- package/skills/gitlab-ops/SKILL.md +7 -6
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/go/SKILL.md +13 -1
- package/skills/npm-publish/SKILL.md +5 -2
- package/skills/plan/SKILL.md +23 -10
- package/skills/plan/mode-feature.md +13 -8
- package/skills/plan/mode-new.md +6 -14
- package/skills/plan/prd-feature-template.md +19 -0
- package/skills/plan/prd-full-template.md +12 -5
- package/skills/plan/prd-premortem-panel.md +48 -0
- package/skills/plan/prd-reviewer-prompt.md +11 -3
- package/skills/plan/soul.md +1 -1
- package/skills/portfolio/SKILL.md +5 -6
- package/skills/quality-gates/SKILL.md +2 -4
- package/skills/reconcile/SKILL.md +27 -4
- package/skills/release/SKILL.md +16 -8
- package/skills/session-end/SKILL.md +94 -11
- package/skills/session-end/phase-3-2-docs-verification.md +1 -0
- package/skills/session-end/phase-3-6-tail.md +11 -7
- package/skills/session-end/phase-3-7a-recommendations.md +9 -2
- package/skills/session-end/plan-verification.md +16 -1
- package/skills/session-end/references/phase-3-documentation-updates.md +14 -12
- package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
- package/skills/session-end/references/session-summary-template.md +4 -0
- package/skills/session-end/session-metrics-write.md +7 -4
- package/skills/session-plan/SKILL.md +5 -3
- package/skills/session-start/SKILL.md +9 -3
- package/skills/session-start/phase-4-5-resource-health.md +11 -5
- package/skills/session-start/phase-8-5-express-path.md +4 -3
- package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
- package/skills/session-start/references/phase-1-5-session-continuity.md +2 -2
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
- package/skills/tmux-layout/SKILL.md +2 -2
- package/skills/vault-sync/validator.mjs +3 -2
- package/skills/wave-executor/SKILL.md +5 -4
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
- package/skills/wave-executor/references/wave-loop-review.md +13 -5
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +0 -47
- package/assets/wave-lifecycle.svg +0 -98
|
@@ -49,13 +49,24 @@ Entered when `$ARGUMENTS` contains `--retroactive`. Writes the lock file and, pe
|
|
|
49
49
|
plugin-version: <current plugin version from $PLUGIN_ROOT/package.json>
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
6. **Patch Session Config (#182).**
|
|
52
|
+
6. **Patch Session Config (#182, #1567).** Find the mandatory fields the `## Session Config` block does not carry and append them with defaults. The mandatory fields are the keys of the FIRST `## Session Config` block in `$PLUGIN_ROOT/docs/session-config-template.md` (today the 7 validated by `scripts/lib/config-schema.mjs`): `test-command`, `typecheck-command`, `lint-command`, `agents-per-wave`, `waves`, `persistence`, `enforcement`.
|
|
53
|
+
|
|
54
|
+
Presence is read from the FILE, not from the validator: `parseSessionConfig()` fills every mandatory field with its default, so `parse-config.mjs` / `validate-config.mjs` never report an ABSENT field (a pnpm repo without `test-command` would silently run `npm test`). `missingStarterKeys()` (`scripts/lib/config/template-keys.mjs`) compares the template's starter keys against `collectTopLevelKeyLines()` — the top-level keys the runtime reads, in every accepted spelling (`key:`, `- key:`, `- **key:**`).
|
|
53
55
|
|
|
54
56
|
```bash
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
MISSING_FIELDS="$(
|
|
57
|
+
CONFIG_FILE="CLAUDE.md"
|
|
58
|
+
[[ -f "AGENTS.md" ]] && CONFIG_FILE="AGENTS.md"
|
|
59
|
+
|
|
60
|
+
MISSING_FIELDS="$(node --input-type=module -e "
|
|
61
|
+
import { readFileSync } from 'node:fs';
|
|
62
|
+
import { missingStarterKeys } from '$PLUGIN_ROOT/scripts/lib/config/template-keys.mjs';
|
|
63
|
+
const missing = missingStarterKeys(
|
|
64
|
+
readFileSync(process.argv[1], 'utf8'),
|
|
65
|
+
readFileSync('$PLUGIN_ROOT/docs/session-config-template.md', 'utf8'),
|
|
66
|
+
);
|
|
67
|
+
if (missing === null) { console.error('template has no Session Config block — mandatory-field check did not run'); process.exit(1); }
|
|
68
|
+
console.log(missing.join(' '));
|
|
69
|
+
" "$CONFIG_FILE")"
|
|
59
70
|
if [[ -n "$MISSING_FIELDS" ]]; then
|
|
60
71
|
# Detect package manager to pick sensible defaults for commands.
|
|
61
72
|
PM_DEFAULTS="$(node --input-type=module -e "
|
|
@@ -67,9 +78,6 @@ Entered when `$ARGUMENTS` contains `--retroactive`. Writes the lock file and, pe
|
|
|
67
78
|
console.log('lint-command: ' + cmds.lint.command);
|
|
68
79
|
" 2>/dev/null)"
|
|
69
80
|
|
|
70
|
-
CONFIG_FILE="CLAUDE.md"
|
|
71
|
-
[[ -f "AGENTS.md" ]] && CONFIG_FILE="AGENTS.md"
|
|
72
|
-
|
|
73
81
|
# Append each missing field under the ## Session Config block.
|
|
74
82
|
for field in $MISSING_FIELDS; do
|
|
75
83
|
case "$field" in
|
|
@@ -89,7 +97,7 @@ Entered when `$ARGUMENTS` contains `--retroactive`. Writes the lock file and, pe
|
|
|
89
97
|
fi
|
|
90
98
|
```
|
|
91
99
|
|
|
92
|
-
This patch is best-effort: existing fields are never overwritten. If no fields are missing, this step is a no-op
|
|
100
|
+
This patch is best-effort: existing fields are never overwritten, and an INVALID value of a present field is left for `parse-config.mjs` to report. If no fields are missing, this step is a no-op; if the check did not run (exit 1), report that line instead of `No config changes.`.
|
|
93
101
|
|
|
94
102
|
7. **Commit.** Stage the lock file (and the patched config file, if it changed) and commit:
|
|
95
103
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: project-code-review
|
|
3
|
-
description: Use this agent when you need to review code changes for convention compliance, security issues, or correctness before merging.
|
|
3
|
+
description: Use this agent when you need to review code changes for convention compliance, security issues, or correctness before merging.
|
|
4
4
|
model: inherit
|
|
5
5
|
color: green
|
|
6
6
|
tools: Read, Grep, Glob
|
|
@@ -16,3 +16,7 @@ Performs read-only review of code changes. Use this agent to:
|
|
|
16
16
|
- Verify adherence to naming conventions and file structure
|
|
17
17
|
|
|
18
18
|
Edit this file to add project-specific review checklists, e.g. banned patterns, required linting rules, security invariants, or architecture constraints.
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
<example>Context: A wave of implementation work has completed and the coordinator needs a review pass before committing. user: "Review the auth module changes" assistant: "I'll use the project-code-review agent to check convention compliance and surface any security concerns." <commentary>Convention enforcement and security review are the project-code-review agent's responsibilities.</commentary></example>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: project-discovery
|
|
3
|
-
description: Use this agent when you need to audit project state, map affected modules, or verify assumptions before implementation.
|
|
3
|
+
description: Use this agent when you need to audit project state, map affected modules, or verify assumptions before implementation.
|
|
4
4
|
model: inherit
|
|
5
5
|
color: cyan
|
|
6
6
|
tools: Read, Grep, Glob, Bash
|
|
@@ -16,3 +16,7 @@ Performs read-only audits of the project codebase. Use this agent to:
|
|
|
16
16
|
- Surface potential conflicts or constraints
|
|
17
17
|
|
|
18
18
|
Edit this file to add project-specific discovery queries, e.g. common file paths, known patterns to grep for, or project-specific terminology to watch for.
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
<example>Context: Before adding a new feature, the coordinator needs to understand existing code paths. user: "Audit the auth flow" assistant: "I'll use the project-discovery agent to map auth modules and identify dependencies." <commentary>Read-only audit work is the project-discovery agent's sweet spot.</commentary></example>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: project-quality-gate
|
|
3
|
-
description: Use this agent when you need to verify a repo meets quality standards before a release, merge, or deployment decision.
|
|
3
|
+
description: Use this agent when you need to verify a repo meets quality standards before a release, merge, or deployment decision.
|
|
4
4
|
model: inherit
|
|
5
5
|
color: blue
|
|
6
6
|
tools: Read, Grep, Glob, Bash
|
|
@@ -16,3 +16,7 @@ Evaluates whether the repo passes project-defined quality criteria. Use this age
|
|
|
16
16
|
- Confirm no blocked patterns or known vulnerabilities are introduced
|
|
17
17
|
|
|
18
18
|
Edit this file to add project-specific gate criteria, e.g. minimum test coverage percentage, required CI checks, or deployment pre-conditions.
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
<example>Context: The session is about to close and the coordinator wants a final quality check. user: "Run the quality gate checks" assistant: "I'll use the project-quality-gate agent to verify tests pass, lint is clean, and coverage thresholds are met." <commentary>Gate decisions — pass/fail with evidence — are the project-quality-gate agent's core output.</commentary></example>
|
|
@@ -42,14 +42,14 @@ PHASE 1 IMPLEMENTED (2026-04-19). Session-end opt-in quality gate. Upstream of `
|
|
|
42
42
|
| 6 | `session-config-parity` | Top-level keys under `## Session Config` in `CLAUDE.md` / `AGENTS.md` | diff against `docs/session-config-template.md`; a missing MANDATORY (minimal-baseline) key is an error, a missing OPT-IN-baseline-only key is a note, a local key absent from the template's Session Config blocks is a warning |
|
|
43
43
|
| 7 | `vault-dir-parity` | `vault-integration.vault-dir` in BOTH `CLAUDE.md` AND `AGENTS.md` | reuse `_parseVaultIntegration`; flag when the two files disagree. Short-circuits to PASS when the two are the SAME file by construction (symlink / same inode / byte-identical generated copy) |
|
|
44
44
|
| 8 | `generated-rule-staleness` *(WARN only)* | `.claude/rules/*.md` with `auto-generated: true` frontmatter | extract `learning-key`; WARN when the key is absent from `.orchestrator/metrics/learnings.jsonl` or its learning's `expires_at` is in the past; skipped silently when no auto-generated rules exist |
|
|
45
|
-
| 9 | `rule-scoping` | `.claude/rules/*.md` frontmatter + `## See Also` footers + `.claude/rules/<name>.md` citations in `CLAUDE.md`/`AGENTS.md` |
|
|
45
|
+
| 9 | `rule-scoping` | `.claude/rules/*.md` frontmatter + `## See Also` footers + `.claude/rules/<name>.md` citations in `CLAUDE.md`/`AGENTS.md` | seven probes: `paths:` frontmatter incl. frontmatter not at line 1 (error, #1449), cited-but-missing rule citations (error), zero-match `globs:` patterns (warn), foreign PascalCase glob tokens (warn), unreadable rule files (warn — surfaced instead of silently skipped), declared fleet-intent globs (**note**, never warn), `globs:` without `paths:` (warn, #1638 — Claude Code loads it always-on); skipped silently when `.claude/rules/` is absent |
|
|
46
46
|
| 10 | `docs-parity` | `docs/components.md` count-claims, Session Config key parity (`docs/session-config-template.md` vs `docs/session-config-reference.md`), `.claude/metrics/` stale-path references in `docs/*.md` / `docs/examples/*.md` | three sub-checks (a/b/c) — see below; skipped silently when `docs/components.md` is absent |
|
|
47
47
|
|
|
48
48
|
Check 3 deliberately scopes to forward-looking sections. Mentions inside "Recently Closed", "Decisions", "Archive", etc. describe history and must not be flagged.
|
|
49
49
|
|
|
50
50
|
Check 5 uses `countSlashCommands()` to count the union of non-hidden `*.md` files directly inside `commands/` and skills under `skills/` whose frontmatter explicitly declares `user-invocable: true`, deduplicated by name. The `commands/` directory is resolved relative to `VAULT_DIR` by default; `--commands-dir <path>` overrides that side of the union, while skills remain relative to `VAULT_DIR`. Either directory alone is sufficient; an existing directory with no commands contributes zero rather than causing a skip.
|
|
51
51
|
|
|
52
|
-
Check 6 (issue #30; severity split per #785 and #1356) extracts the YAML block under `## Session Config` from both the canonical template (`docs/session-config-template.md` by default, override with `--config-template`) and the resolved local instruction file — reading the template TWICE, once per occurrence: `{ occurrence: 'first' }` reaches the "
|
|
52
|
+
Check 6 (issue #30; severity split per #785 and #1356) extracts the YAML block under `## Session Config` from both the canonical template (`docs/session-config-template.md` by default, override with `--config-template`) and the resolved local instruction file — reading the template TWICE, once per occurrence: `{ occurrence: 'first' }` reaches the "Starter config" at the top of the template (the 7 schema-mandatory keys), `{ occurrence: 'last' }` reaches the "Full opt-in baseline" (the full key catalog, a strict superset of the minimal block). A template key missing from the local file is a `session-config-parity` **error** when it belongs to the minimal-baseline set, and a **note** with `probe: "opt-in-gap"` when it is opt-in-baseline-only. A local key absent from those template blocks produces a **warning**: it may be a typo or a template gap, and this check makes no claim about documentation elsewhere or runtime use. When the template carries a single `## Session Config` heading (no separate opt-in block), `'first'` and `'last'` resolve to the same block, every template key is treated as mandatory, and unknown-local-key warnings are skipped. Both fenced YAML (```` ```yaml ... ``` ````) and raw YAML body (up to next `## ` heading) are accepted. The check skips gracefully when the template file is absent, when no instruction file is detected, or when explicitly disabled via `--skip-session-config-parity`.
|
|
53
53
|
|
|
54
54
|
The parity set is **template-driven**: every column-0 YAML key under `## Session Config` in the template (across both occurrences) is checked. As of the gsd Pattern Adoption Quick-Wins bundle (PRD 2026-05-22, issues #517–#521), the template-side keys include four opt-in top-level blocks:
|
|
55
55
|
|
|
@@ -68,7 +68,7 @@ Check 7 (issue #600) is the **only** check that intentionally reads BOTH instruc
|
|
|
68
68
|
|
|
69
69
|
The same predicate guards Check 9's probe 2a, which iterates `['CLAUDE.md', 'AGENTS.md']`: with an aliased `AGENTS.md` it scans `CLAUDE.md` only, because otherwise every dangling `.claude/rules/*.md` citation would be reported TWICE — one defect, two errors, and in `--mode strict` a doubled blocking surface against a file the operator is not allowed to edit.
|
|
70
70
|
|
|
71
|
-
Check 9 (`rule-scoping`) validates `.claude/rules/*.md` frontmatter against the `scripts/lib/rule-loader.mjs` contract, catching the class of defect where a rule silently drifts out of the activation pipeline the loader actually implements.
|
|
71
|
+
Check 9 (`rule-scoping`) validates `.claude/rules/*.md` frontmatter against the `scripts/lib/rule-loader.mjs` contract, catching the class of defect where a rule silently drifts out of the activation pipeline the loader actually implements. Seven probes: **(1) paths-presence** — a frontmatter block that does not open on line 1 (text or a provenance comment before `---`) is an ERROR since #1449: Claude Code ignores it and loads the rule always-on; the probe reports `parseGlobsFrontmatter(...).defect === 'frontmatter-not-at-top'` with the remedy (move the block to line 1, provenance comment after the closing `---`). Otherwise, since #795 `paths:` is a full same-shape alias for `globs:` in `rule-loader.mjs` (`globs:` wins silently when both are present), so a well-formed `paths:`-only rule loads correctly SCOPED and is NOT flagged (#840). The probe fires only when the frontmatter textually carries a top-level `paths:` key AND `parseGlobsFrontmatter` — the loader's own parser, imported rather than re-derived — returns `globs === null` (e.g. a malformed frontmatter line makes it throw); that parse mismatch means the rule may silently load ALWAYS-ON, and is flagged as an error. **(2) cited-but-missing** — `(a)` `.claude/rules/<name>.md` citations inside `CLAUDE.md`/`AGENTS.md` that don't resolve to a file on disk, and `(b)` bare `<name>.md` tokens in a rule's own `## See Also` footer that don't exist as sibling rule files (tokens carrying a path separator, e.g. `../../skills/_shared/state-ownership.md`, are cross-directory references and explicitly out of scope); both flagged as errors. **(3) zero-match-globs** — a `globs:` pattern matching zero files in `git ls-files` (falls back to a manual directory walk when git is unavailable); flagged as a WARNING, not an error, because library/exemplar repos legitimately carry dead stack rules. **(4) foreign-glob** — a glob pattern containing a PascalCase product-like token (regex `[A-Z][a-z]+[A-Z]`, e.g. `WalkAITalkieTests`) — a likely copy-paste leftover from another project's rule scope; flagged as a WARNING. **(5) unreadable-file** — a rule file that could not be read (permissions, a race with a concurrent delete); flagged as a WARNING rather than silently skipped, so a completeness audit does not drop files, and so an unreadable file cannot brick the gate under `mode: hard`. **(6) fleet-intent-glob** — a `globs:` pattern DECLARED as fleet intent goes to `notes[]` (see below), never to `warnings[]`, and the declaration also settles probe 4 for that same pattern: a pattern the rule author declared is by construction not a copy-paste leftover. **(7) globs-without-paths** (#1638) — a line-1 frontmatter with a `globs:` key and no `paths:` key: Claude Code reads only `paths:` and ignores `globs:` without an error, so the rule loads ALWAYS-ON there although `rule-loader.mjs` scopes it for wave injection. Flagged as a WARNING with `probe: "globs-without-paths"`, the line of the `globs:` key and the remedy (add `paths:` with the same patterns); never an error, also not under `--mode strict`. Keyed on the parser's raw `paths` field, so body text and a displaced block (probe 1) never trigger it. Glob matching reuses the same picomatch-with-inline-fallback resolution `scripts/lib/rule-loader.mjs` uses (`parseGlobsFrontmatter` is imported directly; the picomatch resolution itself is duplicated locally since `rule-loader.mjs` does not export a public matcher function). The check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `.claude/rules/` is absent, or explicitly via `--skip-rule-scoping`.
|
|
72
72
|
|
|
73
73
|
**Fleet-intent declaration (Check 9, probe 6).** A rule may declare that a `globs:` pattern is aimed at CONSUMER repos rather than this one, via its own frontmatter key `fleet-intent-globs:`. Accepted forms are block style (`fleet-intent-globs:` plus indented ` - "<pattern>"` lines), flow style (`fleet-intent-globs: ["a", "b"]`) and a single inline value; surrounding quotes are stripped, blank and `#` comment lines are ignored. The key is inert for rule loading — `rule-loader.mjs`'s `parseGlobsFrontmatter` skips unknown top-level keys and their continuation lines. A declared pattern is reported in `notes[]` with its file, the pattern, whether it matches `≥1` or `0` tracked files here, and the declaration `source` (`frontmatter`). The grain is per pattern AND per rule file: declaring one glob amnesties neither a sibling glob in the same rule nor the same pattern in another rule. There is deliberately NO checker-side exemption list: a rule that does not declare the pattern itself is not exempt, so the declaration always sits with the rule it describes.
|
|
74
74
|
|
|
@@ -38,11 +38,13 @@ import { resolveInstructionFile } from '../../scripts/lib/common.mjs';
|
|
|
38
38
|
import { _parseVaultIntegration } from '../../scripts/lib/config/vault-integration.mjs';
|
|
39
39
|
import { _parseDriftCheck } from '../../scripts/lib/config/drift-check.mjs';
|
|
40
40
|
import { isSessionConfigHeading } from '../../scripts/lib/config/section-extractor.mjs';
|
|
41
|
+
import { extractSessionConfigBlock, extractTopLevelKeys } from '../../scripts/lib/config/template-keys.mjs';
|
|
41
42
|
import { FRONTMATTER_NOT_AT_TOP, isRuleExpired, parseGlobsFrontmatter } from '../../scripts/lib/rule-loader.mjs';
|
|
42
43
|
import { resolveRepoSpec } from '../../scripts/lib/vcs-repo-spec.mjs';
|
|
43
44
|
import { userInvocableSkills } from '../../scripts/lib/user-invocable-skills.mjs';
|
|
44
45
|
import { loadOwnerConfig } from '../../scripts/lib/owner-yaml.mjs';
|
|
45
46
|
import { learningKeyOf } from '../../scripts/lib/learnings/kebab.mjs';
|
|
47
|
+
import { resolveLedgerPath } from '../../scripts/lib/ledger-path.mjs';
|
|
46
48
|
|
|
47
49
|
const FORWARD_HEADING_RE =
|
|
48
50
|
/(?:^|\b)(what'?s?\s+next|backlog|open\s+issues?|offene\s+(?:issues?|themen)|todo|next\s+steps?|roadmap)(?:$|\b)/i;
|
|
@@ -133,88 +135,6 @@ function walkDir(dir, visit) {
|
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
/**
|
|
137
|
-
* Extract the YAML block under a `## Session Config` heading from a Markdown
|
|
138
|
-
* document. Tries fenced YAML first (```yaml ... ```) *within the matched
|
|
139
|
-
* heading's own body slice*, then falls back to the raw body up to the next
|
|
140
|
-
* `^## ` heading or EOF.
|
|
141
|
-
*
|
|
142
|
-
* `docs/session-config-template.md` carries TWO literal `## Session Config`
|
|
143
|
-
* lines — both live INSIDE fenced ```yaml example blocks: the "Full minimal
|
|
144
|
-
* baseline" (7 mandatory keys) and the "Full opt-in baseline" (the complete
|
|
145
|
-
* key catalog, a strict superset of the minimal block). By default this
|
|
146
|
-
* function matches the FIRST occurrence (correct for a local CLAUDE.md /
|
|
147
|
-
* AGENTS.md instruction file, which has exactly one real heading). Pass
|
|
148
|
-
* `{ occurrence: 'last' }` to match the LAST occurrence instead — the template
|
|
149
|
-
* call site uses this to reach the opt-in baseline's full key set rather than
|
|
150
|
-
* silently stopping at the 7-key minimal block (issue #785).
|
|
151
|
-
*
|
|
152
|
-
* Returns { body: string, headingLine: number } or null when no heading found.
|
|
153
|
-
*
|
|
154
|
-
* @param {string} content
|
|
155
|
-
* @param {{ occurrence?: 'first'|'last' }} [options]
|
|
156
|
-
*/
|
|
157
|
-
function extractSessionConfigBlock(content, { occurrence = 'first' } = {}) {
|
|
158
|
-
const lines = content.split('\n');
|
|
159
|
-
const headingIdxs = [];
|
|
160
|
-
for (let i = 0; i < lines.length; i++) {
|
|
161
|
-
// SSOT predicate (#968). The previous local `/^##\s+Session Config\b/`
|
|
162
|
-
// was the loosest JS comparator in the repo: no end anchor and `\s+`
|
|
163
|
-
// instead of a single space, so it accepted `## Session Config` (two
|
|
164
|
-
// spaces) and `## Session Config Convention` — both of which the runtime
|
|
165
|
-
// parser rejects. This checker's whole job is to report on the block the
|
|
166
|
-
// runtime reads, so matching a heading the runtime cannot see made it
|
|
167
|
-
// audit a section that does not exist.
|
|
168
|
-
if (isSessionConfigHeading(lines[i])) headingIdxs.push(i);
|
|
169
|
-
}
|
|
170
|
-
if (headingIdxs.length === 0) return null;
|
|
171
|
-
|
|
172
|
-
const targetIdx = occurrence === 'last' ? headingIdxs[headingIdxs.length - 1] : headingIdxs[0];
|
|
173
|
-
const headingLine = targetIdx + 1; // 1-based line number for error reporting
|
|
174
|
-
|
|
175
|
-
// Raw body: from the line after the heading up to the next `## ` heading or EOF.
|
|
176
|
-
let endIdx = lines.length;
|
|
177
|
-
for (let i = targetIdx + 1; i < lines.length; i++) {
|
|
178
|
-
if (/^##\s+/.test(lines[i])) { endIdx = i; break; }
|
|
179
|
-
}
|
|
180
|
-
const rawBody = lines.slice(targetIdx + 1, endIdx).join('\n');
|
|
181
|
-
|
|
182
|
-
// Prefer a fenced YAML block found anywhere within the raw body slice
|
|
183
|
-
// (tolerates a blank line between the heading and the fence); fall back to
|
|
184
|
-
// the raw body itself when no fence is present.
|
|
185
|
-
const fenced = rawBody.match(/```ya?ml\r?\n([\s\S]*?)\r?\n```/);
|
|
186
|
-
const body = fenced ? fenced[1] : rawBody;
|
|
187
|
-
|
|
188
|
-
return { body, headingLine };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Extract top-level YAML keys from a YAML body. Only column-0 keys are
|
|
193
|
-
* collected (indented keys are children and ignored).
|
|
194
|
-
*
|
|
195
|
-
* Accepts four equivalent forms consumer CLAUDE.md files write a Session
|
|
196
|
-
* Config key in: bare `key:`, Markdown bullet `- key:`, bold `**key:**`
|
|
197
|
-
* (closing bold before the colon), and the bold-bullet consumer shape
|
|
198
|
-
* `- **key:** value` (bold wraps the key AND colon together, closing bold
|
|
199
|
-
* AFTER the colon). Baseline issue #60: the original bare-only regex
|
|
200
|
-
* extracted zero keys from bullet-form local files, so session-config-parity
|
|
201
|
-
* (Check 6, below) either false-positived on every mandatory key (local side
|
|
202
|
-
* reads as empty) or, when the TEMPLATE side also used bullet form, passed
|
|
203
|
-
* vacuously (both sides empty, so the diff was always empty). Sibling
|
|
204
|
-
* precedent: `scripts/lib/config/block-header.mjs` recognizes only header
|
|
205
|
-
* lines, not individual key-value pairs — this function is the per-key
|
|
206
|
-
* counterpart.
|
|
207
|
-
*/
|
|
208
|
-
function extractTopLevelKeys(body) {
|
|
209
|
-
const keys = [];
|
|
210
|
-
const re = /^(?:-\s+)?(?:\*\*)?([A-Za-z][\w-]*)(?:\*\*)?:/gm;
|
|
211
|
-
let m;
|
|
212
|
-
while ((m = re.exec(body)) !== null) {
|
|
213
|
-
keys.push(m[1]);
|
|
214
|
-
}
|
|
215
|
-
return keys;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
138
|
/**
|
|
219
139
|
* Extract Session-Config-key-shaped mentions from a "reference" style doc
|
|
220
140
|
* (docs/session-config-reference.md for Check 10b). Unlike the template,
|
|
@@ -632,7 +552,8 @@ function lookupIssueState(iid, repo, cache, vaultDir) {
|
|
|
632
552
|
// legitimately carry dead stack rules.
|
|
633
553
|
// 4. foreign-glob → warnings[]: a glob pattern containing a PascalCase
|
|
634
554
|
// product-like token (e.g. `WalkAITalkieTests`) — a likely copy-paste
|
|
635
|
-
// leftover from another project's rule.
|
|
555
|
+
// leftover from another project's rule. Skipped when the glob matches a
|
|
556
|
+
// `git ls-files` entry of the checked repo — its own directory (#1657).
|
|
636
557
|
// 5. unreadable-file → warnings[]: a rule file that could not be read
|
|
637
558
|
// (permissions, race with a concurrent delete, etc.) — surfaced instead
|
|
638
559
|
// of being silently skipped, since a completeness audit that silently
|
|
@@ -642,16 +563,22 @@ function lookupIssueState(iid, repo, cache, vaultDir) {
|
|
|
642
563
|
// rule's own `fleet-intent-globs:` frontmatter key — reported with its
|
|
643
564
|
// local match count, never warned, and it settles probes 3+4 for that
|
|
644
565
|
// pattern. See FLEET_INTENT_FM_KEY below.
|
|
566
|
+
// 7. globs-without-paths → warnings[]: a line-1 frontmatter with a `globs:`
|
|
567
|
+
// key and no `paths:` key (#1638). Claude Code reads `paths:` only and
|
|
568
|
+
// ignores `globs:` without an error, so the rule loads ALWAYS-ON there
|
|
569
|
+
// even though rule-loader.mjs scopes it for wave injection. WARN, not
|
|
570
|
+
// error, also under strict — the fix is a judgement (which patterns),
|
|
571
|
+
// and the repo's own hard gate is check-rules.mjs, not this check.
|
|
645
572
|
//
|
|
646
573
|
// This list is numeric, not a call sequence — the code below does NOT run
|
|
647
|
-
// 1→2→3→4→5→6 in order. Per rule file: probe 5's read-failure guard runs
|
|
574
|
+
// 1→2→3→4→5→6→7 in order. Per rule file: probe 5's read-failure guard runs
|
|
648
575
|
// FIRST (it gates every other probe via `continue` on an unreadable file,
|
|
649
|
-
// before probe 1 ever sees the content), then probe 1, then probes 3/4 with
|
|
576
|
+
// before probe 1 ever sees the content), then probe 1, then probe 7, then probes 3/4 with
|
|
650
577
|
// probe 6 interleaved INSIDE the same per-pattern loop (it must intercept a
|
|
651
578
|
// declared fleet-intent pattern before 3/4 evaluate it), then probe 2b: once
|
|
652
579
|
// every rule file has been visited, probe 2a runs last over CLAUDE.md/
|
|
653
580
|
// AGENTS.md. `grep -n "Probe [0-9]" checker.mjs` therefore surfaces markers
|
|
654
|
-
// in code order (5, 1, 6, 2b, 2a), not numeric order — that is intentional,
|
|
581
|
+
// in code order (5, 1, 7, 6, 2b, 2a), not numeric order — that is intentional,
|
|
655
582
|
// not drift; do not "fix" it by moving probe 6's marker next to probe 5's.
|
|
656
583
|
// ───────────────────────────────────────────────────────────────────────────
|
|
657
584
|
|
|
@@ -718,6 +645,10 @@ function globMatchesAny(pattern, trackedFiles) {
|
|
|
718
645
|
* 64 MiB buffer exists because the 1 MiB default threw ENOBUFS on a vault
|
|
719
646
|
* listing of 1,199,500 bytes (measured 2026-10-04), which the old bare catch
|
|
720
647
|
* turned into exactly those false warnings, silently.
|
|
648
|
+
*
|
|
649
|
+
* Returns `{ files, fromGit }`: `fromGit` is false when the listing came from
|
|
650
|
+
* the walk fallback. The foreign-glob probe only trusts a git listing to
|
|
651
|
+
* exempt a glob (#1657) — without it, it keeps warning.
|
|
721
652
|
*/
|
|
722
653
|
function listTrackedFiles(vaultDir) {
|
|
723
654
|
try {
|
|
@@ -727,7 +658,7 @@ function listTrackedFiles(vaultDir) {
|
|
|
727
658
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
728
659
|
maxBuffer: 64 * 1024 * 1024,
|
|
729
660
|
});
|
|
730
|
-
return out.split('\n').filter(Boolean);
|
|
661
|
+
return { files: out.split('\n').filter(Boolean), fromGit: true };
|
|
731
662
|
} catch (err) {
|
|
732
663
|
const notARepo = err?.status === 128 && /not a git repository/i.test(String(err?.stderr ?? ''));
|
|
733
664
|
if (err?.code !== 'ENOENT' && !notARepo) {
|
|
@@ -737,7 +668,7 @@ function listTrackedFiles(vaultDir) {
|
|
|
737
668
|
}
|
|
738
669
|
const files = [];
|
|
739
670
|
walkDir(vaultDir, (f) => files.push(relative(vaultDir, f).replace(/\\/g, '/')));
|
|
740
|
-
return files;
|
|
671
|
+
return { files, fromGit: false };
|
|
741
672
|
}
|
|
742
673
|
}
|
|
743
674
|
|
|
@@ -1067,13 +998,13 @@ function main() {
|
|
|
1067
998
|
// and was wrong about two of them:
|
|
1068
999
|
// awk '/^## /{sec=$0} /^auto-skill-dispatch:|^issue-budget:/{print NR,sec}' \
|
|
1069
1000
|
// docs/session-config-template.md
|
|
1070
|
-
// → auto-skill-dispatch
|
|
1071
|
-
// → issue-budget
|
|
1001
|
+
// → auto-skill-dispatch under ## Auto-Skill Dispatch
|
|
1002
|
+
// → issue-budget under ## Issue Budget
|
|
1072
1003
|
// rg -c "'auto-skill-dispatch'" scripts/ → 4 files, non-zero
|
|
1073
1004
|
// rg -c "'issue-budget'" scripts/ → non-zero
|
|
1074
1005
|
// Both are documented in the template FILE and both are read at runtime;
|
|
1075
|
-
// only their absence from the two `## Session Config` blocks (
|
|
1076
|
-
// was ever true. The message below now asserts only that.
|
|
1006
|
+
// only their absence from the two `## Session Config` blocks (starter and
|
|
1007
|
+
// opt-in baseline) was ever true. The message below now asserts only that.
|
|
1077
1008
|
//
|
|
1078
1009
|
// `mode: hard`/`strict` still keys off `errors[]` only, so neither notes
|
|
1079
1010
|
// nor the new warnings can block `runConfigValidationGate()`.
|
|
@@ -1091,10 +1022,11 @@ function main() {
|
|
|
1091
1022
|
const localContent = readFileSync(instr.path, 'utf8');
|
|
1092
1023
|
// 'last' occurrence reaches the "Full opt-in baseline" block (the full
|
|
1093
1024
|
// key catalog, a strict superset of the minimal block) — issue #785.
|
|
1094
|
-
// 'first' occurrence reaches the "
|
|
1095
|
-
// keys) — used here to classify which
|
|
1096
|
-
// opt-in. When the template has only ONE
|
|
1097
|
-
// (e.g. a test fixture, or a repo that never
|
|
1025
|
+
// 'first' occurrence reaches the "Starter config" (formerly "Full
|
|
1026
|
+
// minimal baseline"; 7 mandatory keys) — used here to classify which
|
|
1027
|
+
// missing keys are mandatory vs opt-in. When the template has only ONE
|
|
1028
|
+
// `## Session Config` heading (e.g. a test fixture, or a repo that never
|
|
1029
|
+
// split the two baselines),
|
|
1098
1030
|
// 'first' and 'last' resolve to the SAME block, so every key is
|
|
1099
1031
|
// treated as mandatory — preserving pre-existing behaviour for
|
|
1100
1032
|
// single-block templates.
|
|
@@ -1297,7 +1229,7 @@ function main() {
|
|
|
1297
1229
|
// Build the set of known learning keys from learnings.jsonl.
|
|
1298
1230
|
// key → expires_at (ISO string or null)
|
|
1299
1231
|
const knownKeys = new Map();
|
|
1300
|
-
const learningsPath =
|
|
1232
|
+
const learningsPath = resolveLedgerPath('.orchestrator/metrics/learnings.jsonl', vaultDir);
|
|
1301
1233
|
const learningsPresent = existsSync(learningsPath) && statSync(learningsPath).isFile();
|
|
1302
1234
|
|
|
1303
1235
|
if (learningsPresent) {
|
|
@@ -1341,6 +1273,7 @@ function main() {
|
|
|
1341
1273
|
check: 'generated-rule-staleness',
|
|
1342
1274
|
file: relPath,
|
|
1343
1275
|
line: 1,
|
|
1276
|
+
// ledger-root: message text naming the ledger, not a path read
|
|
1344
1277
|
message: `Auto-generated rule references learning-key '${learningKey}' which is absent from .orchestrator/metrics/learnings.jsonl, and the rule carries no valid evidence-digest to verify it offline`,
|
|
1345
1278
|
extracted: learningKey,
|
|
1346
1279
|
});
|
|
@@ -1382,7 +1315,7 @@ function main() {
|
|
|
1382
1315
|
|
|
1383
1316
|
// Check 9: rule-scoping — validates .claude/rules/*.md frontmatter against
|
|
1384
1317
|
// the rule-loader.mjs contract (see the doc-comment above the helper
|
|
1385
|
-
// functions for the
|
|
1318
|
+
// functions for the seven probes, incl. unreadable-file and fleet-intent-glob). Silently skipped (no id pushed, no
|
|
1386
1319
|
// checksSkipped entry) when .claude/rules/ is absent — mirrors Check 8's
|
|
1387
1320
|
// silent-skip semantics. `--skip-rule-scoping` disables the whole check.
|
|
1388
1321
|
if (!args.skipRuleScoping) {
|
|
@@ -1399,6 +1332,7 @@ function main() {
|
|
|
1399
1332
|
|
|
1400
1333
|
// Lazily computed — only needed once any rule declares ≥1 glob pattern.
|
|
1401
1334
|
let trackedFiles = null;
|
|
1335
|
+
let trackedFromGit = false;
|
|
1402
1336
|
|
|
1403
1337
|
for (const fname of ruleFileNames) {
|
|
1404
1338
|
const absPath = join(rulesDir, fname);
|
|
@@ -1457,10 +1391,30 @@ function main() {
|
|
|
1457
1391
|
});
|
|
1458
1392
|
}
|
|
1459
1393
|
|
|
1394
|
+
// --- Probe 7: globs-without-paths → warnings[] (#1638) ---
|
|
1395
|
+
// Keyed on the parser's raw `paths` field, never on the merged `globs`
|
|
1396
|
+
// value or on body text: `parsed.globs !== null` with `parsed.paths ===
|
|
1397
|
+
// null` means the frontmatter carries `globs:` and no `paths:`. A
|
|
1398
|
+
// displaced or unparseable block yields `globs: null` and stays silent
|
|
1399
|
+
// here (probe 1 owns the displaced case).
|
|
1400
|
+
if (parsed.globs !== null && parsed.paths === null) {
|
|
1401
|
+
// Search the frontmatter only (line 2 up to the closing ---), with the
|
|
1402
|
+
// parser's own key tolerance (`globs :`), so a body line never wins.
|
|
1403
|
+
const fmLines = content.split(/\r?\n/);
|
|
1404
|
+
const fmEnd = fmLines.indexOf('---', 1);
|
|
1405
|
+
const globsLine = fmLines.slice(0, fmEnd === -1 ? fmLines.length : fmEnd)
|
|
1406
|
+
.findIndex((l, i) => i > 0 && /^globs\s*:/.test(l)) + 1;
|
|
1407
|
+
warnings.push({
|
|
1408
|
+
check: 'rule-scoping', probe: 'globs-without-paths', file: relPath, line: globsLine || 1,
|
|
1409
|
+
message: `${relPath}: frontmatter has 'globs:' but no 'paths:' — Claude Code reads only 'paths:', so this rule loads ALWAYS-ON for Claude Code. Add 'paths:' with the matching patterns (the same list as 'globs:').`,
|
|
1410
|
+
extracted: 'globs:',
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1460
1414
|
// --- Probes 3 & 4: zero-match-globs / foreign-glob → warnings[] ---
|
|
1461
1415
|
const globs = parsed.globs;
|
|
1462
1416
|
if (Array.isArray(globs) && globs.length > 0) {
|
|
1463
|
-
if (trackedFiles === null) trackedFiles = listTrackedFiles(vaultDir);
|
|
1417
|
+
if (trackedFiles === null) ({ files: trackedFiles, fromGit: trackedFromGit } = listTrackedFiles(vaultDir));
|
|
1464
1418
|
const declaredFleetIntent = new Set(parseFleetIntentGlobs(fmBody));
|
|
1465
1419
|
for (const pattern of globs) {
|
|
1466
1420
|
// --- Probe 6: fleet-intent-glob → notes[] ---
|
|
@@ -1480,14 +1434,19 @@ function main() {
|
|
|
1480
1434
|
});
|
|
1481
1435
|
continue;
|
|
1482
1436
|
}
|
|
1483
|
-
|
|
1437
|
+
const matchesTracked = globMatchesAny(pattern, trackedFiles);
|
|
1438
|
+
if (!matchesTracked) {
|
|
1484
1439
|
warnings.push({
|
|
1485
1440
|
check: 'rule-scoping', file: relPath, line: 1,
|
|
1486
1441
|
message: `glob '${pattern}' matches 0 tracked files (library/exemplar repos may legitimately carry dead stack rules)`,
|
|
1487
1442
|
extracted: pattern,
|
|
1488
1443
|
});
|
|
1489
1444
|
}
|
|
1490
|
-
|
|
1445
|
+
// #1657: a glob that matches a git-tracked file of THIS repo names
|
|
1446
|
+
// the repo's own directory (`WalkAITalkie/**` in WalkAITalkie), not
|
|
1447
|
+
// a copy-paste leftover. Only a git listing exempts — the walk
|
|
1448
|
+
// fallback keeps warning (fail toward reporting).
|
|
1449
|
+
if (FOREIGN_GLOB_TOKEN_RE.test(pattern) && !(trackedFromGit && matchesTracked)) {
|
|
1491
1450
|
warnings.push({
|
|
1492
1451
|
check: 'rule-scoping', file: relPath, line: 1,
|
|
1493
1452
|
message: `glob '${pattern}' contains a PascalCase product-like token — verify this rule was not copy-pasted from another project`,
|
|
@@ -1687,6 +1646,7 @@ function main() {
|
|
|
1687
1646
|
if (metricsLines[i].includes('.claude/metrics/')) {
|
|
1688
1647
|
errors.push({
|
|
1689
1648
|
check: 'docs-parity', file: rel, line: i + 1,
|
|
1649
|
+
// ledger-root: message text naming the canonical dir, not a path read
|
|
1690
1650
|
message: `Stale metrics path '.claude/metrics/' referenced — canonical path is '.orchestrator/metrics/'`,
|
|
1691
1651
|
extracted: '.claude/metrics/',
|
|
1692
1652
|
});
|
package/skills/close/SKILL.md
CHANGED
|
@@ -13,15 +13,28 @@ The user invokes `/close` (arguments: **$ARGUMENTS** — none are interpreted; a
|
|
|
13
13
|
|
|
14
14
|
The user wants to end the current session.
|
|
15
15
|
|
|
16
|
-
**Pre-check before invoking session-end
|
|
16
|
+
**Pre-check before invoking session-end (#1661).** Run, from the project root:
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **No matching completed record exists:** Warn the user: "STATE.md completed ohne passenden Abschluss im Ledger — session-end wird zur Vervollständigung ausgeführt (#429)." An abandoned backfill stub does not prove finalization. Do NOT stop — proceed to invoke the session-end skill exactly as in exit condition 3, so the missing completion gets written this time. (The SessionEnd hook's `backfillCompletedFromStateMd` independently self-heals this same gap on a later session's teardown; this Pre-Check branch covers the case where /close is re-run before that hook has had a chance to fire.)
|
|
22
|
-
3. **STATE.md exists and `status: active` or `status: paused`:** Proceed to invoke the session-end skill.
|
|
18
|
+
```bash
|
|
19
|
+
node "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/close-precheck.mjs" --repo-root "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
20
|
+
```
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
It prints one JSON object (exit 0 for every verdict). It reads `<state-dir>/STATE.md` for the current platform (`.claude/`, `.codex/`, `.cursor/`, `.pi/`) and decides **whose** it is before it looks at `status`: STATE.md `session-id` (#1368) against this process's native id (`readProcessLocalSessionIds()`). Never decide ownership from the session lock: it records the pid of the short-lived hook process, and in a shared checkout it can name a peer. After `/clear` the tab runs under a NEW native id, so the STATE.md of the work before `/clear` reads as foreign — measured 2026-10-17 in three sessions. A STATE.md without `session-id` (before #1368) or a process without a native id (`ownership: legacy` / `unprovable`) never reaches session-end on its status alone (#1718): where the status would hand off, the verdict is `ownership-unproven`. An optional `--confirm-own <confirmToken>` (16 lowercase hex, from that verdict) re-runs the pre-check for the unchanged file. Act on `verdict`:
|
|
23
|
+
|
|
24
|
+
1. **`run-session-end`** — own STATE.md, or one confirmed with `--confirm-own` (`ownership: confirmed`), `status: active` or `paused`: invoke the session-end skill.
|
|
25
|
+
2. **`complete-missing-record`** — own or confirmed STATE.md `status: completed`, but `.orchestrator/metrics/sessions.jsonl` holds no completed record for it (#429: `completed` alone does not prove the Phase 3.7 writer ran; an abandoned backfill stub is no completion). Warn: "STATE.md completed ohne passenden Abschluss im Ledger — session-end wird zur Vervollständigung ausgeführt (#429)." and invoke the session-end skill. (The SessionEnd hook's `backfillCompletedFromStateMd` heals the same gap on a later teardown; this branch covers a `/close` re-run before that.)
|
|
26
|
+
3. **`already-finalized`** — a matching completed record exists (a legacy record without `status` counts): "Previous session was already finalized by session-end. Start a new session with `/session`, or inspect `<state-dir>/STATE.md` to review the prior close. (Note: if this is unexpected after an Express Path session, `skills/go/SKILL.md` should have auto-invoked /close — check that STATE.md `## Deviations` contains an `Express path:` entry.)" Stop.
|
|
27
|
+
4. **`foreign`** — STATE.md belongs to session `stateSessionId`, not to this one, whatever its `status`. Never invoke session-end on it and never edit it: an `active`/`paused` one is a peer's live session, a `completed` one an earlier tab or a peer. Inform: "STATE.md gehört Session `<stateSessionId>` (status `<status>`), nicht dieser — kein eigener Plan zum Abschließen." With `status: active` or `paused` add: "Ist das der Plan dieses Tabs von vor einem `/clear`, kann ihn dieser Prozess nicht als eigenen belegen; er bleibt offen, der SessionEnd-Hook füllt den Ledger nach." Stop.
|
|
28
|
+
5. **`ownership-unproven`** — nobody can prove this STATE.md is this session's (`ownership` `legacy`: no `session-id`; or `unprovable`: this process has no native id, as on Cursor, Pi, Codex without a thread id); session-end would run `wouldRun`. Never invoke session-end on this result and never edit the file. Inform with the facts from the JSON: "STATE.md `<statePath>` ist keiner Session nachweisbar zugeordnet (ownership `<ownership>`, session `<session>`, session-id `<stateSessionId>`, started_at `<startedAt>`, status `<status>`); session-end würde `<wouldRun>` ausführen." — the `session-id` part only when `stateSessionId` is present: without a native id (`unprovable`) a STATE.md that names another session's id comes back here, and the operator must see that. Confirm ONLY when (a) the operator explicitly answers that this is this session's plan — ask via `AskUserQuestion` on Claude Code (a numbered list on Codex/Cursor/Pi, per `.claude/rules/ask-via-tool.md`), the question naming path, session, `session-id` (when present), started_at and status; or (b) first-person evidence: this session's OWN tool call that wrote exactly this STATE.md is visible in its current context (for example its own wave-executor initialised it in this conversation). A compaction summary, a handover file, memory notes or the STATE.md content itself are NOT first-person evidence, and neither is anything inferred from the `session` label, `started_at` or the session lock, which are repo-wide. With a native id (Claude Code, Codex with a thread id) a `legacy` file is usually an older or foreign file — writers stamp `session-id` whenever this process's own lock exists at write time, so an own legacy file arises only when the STATE.md was written without an own lock; expect (b) to apply rarely there. A Lead-led or headless session without (b) never confirms: it stops and reports. On confirmation, re-run the pre-check with `--confirm-own <confirmToken>` and act on the NEW verdict per items 1/2. If it again returns `ownership-unproven` with `reason` (the file changed), show the new facts and decide again; never reuse an old token. Otherwise the plan stays untouched: it belongs to another or an earlier session; start an own `/session`, or inspect the file. Stop.
|
|
29
|
+
6. **`no-state`** — read Session Config `persistence`. `false`: "Session completed (persistence is off — STATE.md was never created). Use `/session` to start a new session." Otherwise: "No active session found. Use `/session` to start a session first." Stop.
|
|
30
|
+
7. **`unexpected-status`** (e.g. `idle`): "Unexpected session status `<status>`. Inspect `<state-dir>/STATE.md` and use `/session` to reset if needed." Stop.
|
|
31
|
+
8. **`inconclusive`** — STATE.md frontmatter or the ledger is unreadable (`reason`): report it and stop before repeating any close side effect.
|
|
32
|
+
|
|
33
|
+
**`/close` ohne Plan.** Every stop above (3–8) leaves this session without an own plan to close — the wait case `close-ohne-plan`. With fleet integration, before the report run the queue-mark re-check on the own handover (`skills/session-end/SKILL.md` § 4.5, "Queue marks on the own handover", #1639); its JSON `file` says whether this session has an own handover or train manifest. Then apply `skills/_shared/fleet-protocol.md` § Rücklauf an den Navigator (Pflicht) with the wait case `close-ohne-plan` (exactly one report in this turn, only with fleet integration, fallback chain up to `Meldung gescheitert: <Grund>`, never wait for a reply), and end the answer — after the message quoted above — with exactly one closing sentence (none of those messages is recognised as a waiting answer on its own):
|
|
34
|
+
- `Bereit zum Schließen.` only on `already-finalized`, `no-state`, `unexpected-status`, or a `foreign` or `ownership-unproven` STATE.md with `status: completed`, and only when the re-check found an own handover (`file` not `null`) and reported no unmarked points (exit 0): the work of this session is handed over, and the coordinator may clear the tab (fleet-protocol § Übergabe und Zug-Manifest, Punkt 3). A `foreign` stop is the normal end of a session that worked after `/clear` without its own `/session`.
|
|
35
|
+
- `Ich warte auf den nächsten Auftrag.` otherwise — no own handover, unmarked points (exit 3), no fleet integration, `inconclusive` (the status is unknown), or a `foreign` or `ownership-unproven` STATE.md that is still `active`/`paused` (a plan that is still open must not let the coordinator clear the tab).
|
|
36
|
+
|
|
37
|
+
session-end itself never runs on this path. A session that only waits on a pipeline or release train closes normally: its train manifest belongs to the waiting answer BEFORE `/close` (same section, Übergabe und Zug-Manifest), and session-end Phase 4.5 reports the case `zug-<name>` once in the `/close` turn.
|
|
25
38
|
|
|
26
39
|
If the pre-check passes, invoke the session-end skill.
|
|
27
40
|
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
import { existsSync, readdirSync, statSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
35
|
-
import { join } from 'node:path';
|
|
35
|
+
import { dirname, join } from 'node:path';
|
|
36
|
+
import { resolveLedgerPath } from '../../../scripts/lib/ledger-path.mjs';
|
|
36
37
|
|
|
37
38
|
// ---------------------------------------------------------------------------
|
|
38
39
|
// Constants
|
|
@@ -70,8 +71,8 @@ function listMarkdownFiles(dirPath) {
|
|
|
70
71
|
return entries.filter((e) => e.isFile() && e.name.endsWith('.md')).map((e) => e.name);
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
function
|
|
74
|
-
return
|
|
74
|
+
function makeLedgerPath(projectRoot) {
|
|
75
|
+
return resolveLedgerPath('.orchestrator/metrics/docs-staleness.jsonl', projectRoot);
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
// ---------------------------------------------------------------------------
|
|
@@ -158,7 +159,8 @@ export async function runProbe(projectRoot, config) {
|
|
|
158
159
|
|
|
159
160
|
// ── JSONL append ──────────────────────────────────────────────────────
|
|
160
161
|
try {
|
|
161
|
-
const
|
|
162
|
+
const ledgerPath = makeLedgerPath(projectRoot);
|
|
163
|
+
const metricsDir = dirname(ledgerPath);
|
|
162
164
|
if (!existsSync(metricsDir)) mkdirSync(metricsDir, { recursive: true });
|
|
163
165
|
const record = {
|
|
164
166
|
timestamp: new Date().toISOString(),
|
|
@@ -175,7 +177,7 @@ export async function runProbe(projectRoot, config) {
|
|
|
175
177
|
threshold_days: f.evidence.threshold_days ?? threshold,
|
|
176
178
|
})),
|
|
177
179
|
};
|
|
178
|
-
appendFileSync(
|
|
180
|
+
appendFileSync(ledgerPath, JSON.stringify(record) + '\n', 'utf8');
|
|
179
181
|
} catch {
|
|
180
182
|
// JSONL write failure is non-fatal — probe result is still returned
|
|
181
183
|
}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
30
30
|
import { join } from 'node:path';
|
|
31
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
31
32
|
|
|
32
33
|
/** Max files to scan before sampling — protects against huge trees. */
|
|
33
34
|
const MAX_FILES = 2000;
|
|
@@ -127,7 +128,10 @@ export default async function frontendSlop({ repoRoot }) {
|
|
|
127
128
|
|
|
128
129
|
// Defensive import — mirrors supply-chain-slopcheck's pattern so the probe
|
|
129
130
|
// never hard-crashes the discovery run if the detector is absent.
|
|
130
|
-
|
|
131
|
+
// The detector ships with the PLUGIN, not with the scanned repo: resolve it
|
|
132
|
+
// from this module, never from repoRoot (#1653 — in every consumer repo the
|
|
133
|
+
// repoRoot path is absent and the probe skipped silently).
|
|
134
|
+
const detectPath = fileURLToPath(new URL('../../../scripts/lib/frontend-detect/detect.mjs', import.meta.url));
|
|
131
135
|
if (!existsSync(detectPath)) {
|
|
132
136
|
return {
|
|
133
137
|
probe,
|
|
@@ -137,7 +141,7 @@ export default async function frontendSlop({ repoRoot }) {
|
|
|
137
141
|
title: 'frontend-slop: detector module unavailable',
|
|
138
142
|
evidence: { file: detectPath, classification: 'SKIPPED' },
|
|
139
143
|
recommendation:
|
|
140
|
-
'scripts/lib/frontend-detect/detect.mjs is missing.
|
|
144
|
+
'scripts/lib/frontend-detect/detect.mjs is missing from the plugin install. Reinstall or update the plugin.',
|
|
141
145
|
},
|
|
142
146
|
],
|
|
143
147
|
summary: { total: 0, filesScanned: 0, sampled: false, errors: 0, skipped_reason: 'detector-unavailable' },
|
|
@@ -146,7 +150,7 @@ export default async function frontendSlop({ repoRoot }) {
|
|
|
146
150
|
|
|
147
151
|
let detectFiles, SCANNABLE_EXTS;
|
|
148
152
|
try {
|
|
149
|
-
({ detectFiles, SCANNABLE_EXTS } = await import(detectPath));
|
|
153
|
+
({ detectFiles, SCANNABLE_EXTS } = await import(pathToFileURL(detectPath).href));
|
|
150
154
|
} catch (err) {
|
|
151
155
|
return {
|
|
152
156
|
probe,
|
|
@@ -45,8 +45,9 @@
|
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
import { existsSync, readdirSync, readFileSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
48
|
-
import { join } from 'node:path';
|
|
48
|
+
import { dirname, join } from 'node:path';
|
|
49
49
|
import { slashCommandNames } from '../../../scripts/lib/user-invocable-skills.mjs';
|
|
50
|
+
import { resolveLedgerPath } from '../../../scripts/lib/ledger-path.mjs';
|
|
50
51
|
|
|
51
52
|
// ---------------------------------------------------------------------------
|
|
52
53
|
// Doc source sets (deliberately small and hand-curated)
|
|
@@ -277,7 +278,8 @@ export async function runProbe(projectRoot, config) {
|
|
|
277
278
|
|
|
278
279
|
// ── JSONL append ──────────────────────────────────────────────────────
|
|
279
280
|
try {
|
|
280
|
-
const
|
|
281
|
+
const ledgerPath = resolveLedgerPath('.orchestrator/metrics/ssot-code-diff.jsonl', projectRoot);
|
|
282
|
+
const metricsDir = dirname(ledgerPath);
|
|
281
283
|
if (!existsSync(metricsDir)) mkdirSync(metricsDir, { recursive: true });
|
|
282
284
|
const record = {
|
|
283
285
|
timestamp: new Date().toISOString(),
|
|
@@ -296,7 +298,7 @@ export async function runProbe(projectRoot, config) {
|
|
|
296
298
|
registry_id: f.evidence.registry_id,
|
|
297
299
|
})),
|
|
298
300
|
};
|
|
299
|
-
appendFileSync(
|
|
301
|
+
appendFileSync(ledgerPath, JSON.stringify(record) + '\n', 'utf8');
|
|
300
302
|
} catch {
|
|
301
303
|
// JSONL write failure is non-fatal — probe result is still returned
|
|
302
304
|
}
|