session-orchestrator 5.3.0 → 5.5.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/claude-md-drift-check/SKILL.md +1 -1
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/060-evolve.mdc +28 -13
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +1 -1
- package/AGENTS.md +9 -31
- package/CHANGELOG.md +162 -0
- package/README.md +14 -10
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +10 -1
- package/docs/ci-setup.md +154 -4
- package/docs/components.md +11 -11
- package/docs/events-schema.md +25 -19
- package/docs/install.md +14 -2
- package/docs/migration-v4.md +24 -17
- package/docs/persona-panel.md +5 -4
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +27 -12
- package/docs/scope-collision-guard.md +40 -16
- package/docs/session-config-reference.md +61 -12
- package/docs/session-config-template.md +11 -2
- package/docs/vault-docs-architecture.md +8 -4
- package/hooks/_lib/hook-import-set.json +42 -29
- package/hooks/_lib/lock-bootstrap.mjs +118 -26
- package/hooks/config-protection.mjs +1 -1
- package/hooks/enforce-commands.mjs +28 -13
- package/hooks/enforce-scope.mjs +201 -23
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/on-session-start.mjs +57 -2
- package/hooks/on-stop.mjs +70 -168
- package/hooks/post-bash-write-verify.mjs +98 -17
- package/hooks/post-edit-validate.mjs +15 -5
- package/hooks/post-tool-batch-wave-signal.mjs +32 -152
- package/hooks/pre-bash-memory-propose-audit.mjs +71 -10
- package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
- package/hooks/pre-bash-staging-fence.mjs +29 -8
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +1143 -279
- package/hooks/subagent-telemetry.mjs +96 -19
- package/hooks/wave-scope-commit-guard.mjs +168 -72
- package/package.json +2 -6
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +6 -1
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +5 -1
- package/rules/opt-in-stack/backend-data.md +9 -1
- package/rules/opt-in-stack/backend.md +7 -1
- package/rules/opt-in-stack/frontend.md +6 -1
- package/rules/opt-in-stack/security-web.md +7 -1
- package/rules/opt-in-stack/swift.md +5 -1
- package/scripts/apply-session-learnings.mjs +513 -0
- package/scripts/archive-closed-prds.mjs +40 -12
- package/scripts/autopilot.mjs +57 -54
- package/scripts/backfill-abandoned-sessions.mjs +81 -6
- package/scripts/backfill-learnings-from-vault.mjs +49 -22
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/codex-install.mjs +3 -0
- package/scripts/cursor-install.mjs +47 -6
- package/scripts/emit-session.mjs +298 -2
- package/scripts/export-hw-learnings.mjs +40 -10
- package/scripts/fleet-checkin.mjs +170 -0
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +6 -1
- package/scripts/lib/autopilot/loop.mjs +32 -13
- package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/blocked-commands-policy.mjs +40 -0
- package/scripts/lib/common.mjs +41 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +7 -6
- package/scripts/lib/config/vault-integration.mjs +151 -16
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +75 -10
- package/scripts/lib/eval/engine.mjs +13 -0
- package/scripts/lib/events-retention-banner.mjs +393 -0
- package/scripts/lib/events-rotation.mjs +21 -6
- package/scripts/lib/events.mjs +72 -5
- package/scripts/lib/fleet-protocol.mjs +288 -0
- package/scripts/lib/gates/gate-baseline.mjs +13 -3
- package/scripts/lib/gates/gate-full.mjs +7 -2
- package/scripts/lib/gates/gate-helpers.mjs +93 -2
- package/scripts/lib/gates/gate-incremental.mjs +23 -22
- package/scripts/lib/gates/gate-per-file.mjs +16 -7
- package/scripts/lib/hardening.mjs +1 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
- package/scripts/lib/instruction-budget-guard.mjs +77 -11
- package/scripts/lib/learnings/expiry-sweep.mjs +420 -22
- package/scripts/lib/learnings/io.mjs +274 -37
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/lock-reaper.mjs +38 -14
- package/scripts/lib/maintenance-due-banner.mjs +23 -8
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +147 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +323 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +144 -0
- package/scripts/lib/orphan-reaper/scan.mjs +510 -0
- package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
- package/scripts/lib/orphan-reaper.mjs +47 -1315
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/platform.mjs +206 -2
- package/scripts/lib/plugin-update-banner.mjs +3 -1
- package/scripts/lib/process-group.mjs +134 -22
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +45 -5
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +100 -62
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +119 -29
- package/scripts/lib/scope-echo.mjs +7 -0
- package/scripts/lib/scope-gate.mjs +194 -27
- package/scripts/lib/session-close-backfill.mjs +129 -4
- package/scripts/lib/session-end/tail-runner.mjs +10 -5
- package/scripts/lib/session-lock-shape.mjs +6 -3
- package/scripts/lib/session-lock.mjs +320 -67
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +67 -17
- package/scripts/lib/session-schema/constants.mjs +30 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-start-probes.mjs +242 -68
- package/scripts/lib/session-token-rollup.mjs +252 -54
- package/scripts/lib/sessions-canonical.mjs +100 -9
- package/scripts/lib/sessions-staleness-banner.mjs +3 -1
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +27 -3
- package/scripts/lib/telemetry/pricing.mjs +121 -71
- package/scripts/lib/telemetry/sync.mjs +17 -0
- package/scripts/lib/tests-src-ratio.mjs +215 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/ux-grill/reconcile.mjs +1 -1
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +388 -27
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
- package/scripts/lib/validate-vendored-rules.mjs +206 -81
- package/scripts/lib/vault-mirror/auto-commit.mjs +5 -3
- package/scripts/lib/vault-mirror/namespace.mjs +47 -26
- package/scripts/lib/vault-mirror/process.mjs +350 -62
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +14 -5
- package/scripts/lib/vault-mirror/render-learnings.mjs +66 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +28 -4
- package/scripts/lib/vault-mirror/telemetry.mjs +25 -11
- package/scripts/lib/vault-status/board-writer.mjs +53 -12
- package/scripts/lib/vault-status/narrative-mirror.mjs +40 -9
- package/scripts/lib/vcs-repo-spec.mjs +8 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/materialize-wave-scope.mjs +7 -4
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +2 -1
- package/scripts/release.mjs +11 -3
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/run-migrate-v2-cross-repo.mjs +171 -31
- package/scripts/run-quality-gate.mjs +331 -307
- package/scripts/self-update.mjs +229 -0
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +603 -20
- package/scripts/sweep-expired-rules.mjs +2 -1
- package/scripts/validate-plugin.mjs +7 -0
- package/scripts/validate-wave-scope.mjs +21 -2
- package/scripts/vault-backfill.mjs +50 -37
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +238 -75
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +134 -0
- package/skills/_shared/state-ownership.md +41 -12
- package/skills/autopilot/SKILL.md +25 -11
- package/skills/bootstrap/SKILL.md +6 -4
- package/skills/bootstrap/_shared-template.md +97 -2
- package/skills/bootstrap/fast-template.md +4 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/bootstrap/standard-template.md +2 -0
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/discovery/probes/ssot-code-diff.mjs +12 -6
- package/skills/discovery/probes/vault-narrative-staleness.mjs +6 -2
- package/skills/discovery/probes/vault-staleness.mjs +9 -3
- package/skills/docs-orchestrator/SKILL.md +12 -5
- package/skills/docs-orchestrator/audience-mapping.md +5 -3
- package/skills/evolve/SKILL.md +13 -4
- package/skills/evolve/references/evolve-analyze-mode.md +42 -18
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/learning-patterns.md +23 -13
- package/skills/session-end/metrics-collection.md +6 -7
- package/skills/session-end/phase-3-2-docs-verification.md +7 -6
- package/skills/session-end/plan-verification.md +25 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +13 -3
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +65 -68
- package/skills/session-plan/SKILL.md +0 -3
- package/skills/session-start/SKILL.md +8 -2
- package/skills/session-start/references/operations-contract.md +6 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
- 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 +10 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/vault-mirror/SKILL.md +21 -11
- package/skills/wave-executor/SKILL.md +2 -2
- package/skills/wave-executor/circuit-breaker.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -1
- package/skills/wave-executor/references/wave-loop-review.md +4 -6
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +13 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Use when detecting drift between CLAUDE.md (or AGENTS.md, the Codex CLI alias) / _meta narrative and
|
|
5
5
|
live repository state. Ten checks: absolute-path resolution, 01-projects/ count claims, issue-reference
|
|
6
6
|
freshness, session-file existence, command-count sync, session-config-parity (mandatory template keys =
|
|
7
|
-
error, opt-in gaps = warning), vault-dir-parity (CLAUDE.md vs AGENTS.md), generated-rule-staleness
|
|
7
|
+
error, opt-in gaps = note, unknown local keys = warning), vault-dir-parity (CLAUDE.md vs AGENTS.md), generated-rule-staleness
|
|
8
8
|
(WARN-only), rule-scoping (paths:/globs: frontmatter defects, dangling rule citations, zero-match
|
|
9
9
|
globs), and docs-parity (docs/components.md count-claims vs on-disk counts, template-vs-reference
|
|
10
10
|
config-key parity, stale .claude/metrics/ paths). Full per-check spec in the body table. Invoked as an
|
|
@@ -37,18 +37,18 @@ PHASE 1 IMPLEMENTED (2026-04-19). Session-end opt-in quality gate. Upstream of `
|
|
|
37
37
|
| 2 | `project-count-sync` | Hardcoded "N registered" / "N projects" claims next to `01-projects/` | compare to `ls -d 01-projects/*/` |
|
|
38
38
|
| 3 | `issue-reference-freshness` | `#NN` in forward-looking sections (What's Next, Backlog, Open Issues, Offene Themen, Todo, Next Steps) | `glab issue view NN --repo <origin>` |
|
|
39
39
|
| 4 | `session-file-existence` | `50-sessions/YYYY-MM-DD-*.md` references anywhere in scope | `existsSync(vault/50-sessions/<file>)` |
|
|
40
|
-
| 5 | `command-count` | "N commands" / "N /commands" claims in prose | compare to `
|
|
41
|
-
| 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 warning |
|
|
40
|
+
| 5 | `command-count` | "N commands" / "N /commands" claims in prose | compare to the name-deduplicated union of `commands/*.md` and skills with explicit `user-invocable: true`; skipped only when both directories are absent |
|
|
41
|
+
| 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 |
|
|
42
42
|
| 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) |
|
|
43
43
|
| 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 |
|
|
44
|
-
| 9 | `rule-scoping` | `.claude/rules/*.md` frontmatter + `## See Also` footers + `.claude/rules/<name>.md` citations in `CLAUDE.md`/`AGENTS.md` | six probes: `paths:` frontmatter (error), 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); skipped silently when `.claude/rules/` is absent |
|
|
44
|
+
| 9 | `rule-scoping` | `.claude/rules/*.md` frontmatter + `## See Also` footers + `.claude/rules/<name>.md` citations in `CLAUDE.md`/`AGENTS.md` | six 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); skipped silently when `.claude/rules/` is absent |
|
|
45
45
|
| 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 |
|
|
46
46
|
|
|
47
47
|
Check 3 deliberately scopes to forward-looking sections. Mentions inside "Recently Closed", "Decisions", "Archive", etc. describe history and must not be flagged.
|
|
48
48
|
|
|
49
|
-
Check 5
|
|
49
|
+
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.
|
|
50
50
|
|
|
51
|
-
Check 6 (issue #30; severity split per #785) 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 "Full minimal baseline" (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 **
|
|
51
|
+
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 "Full minimal baseline" (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`.
|
|
52
52
|
|
|
53
53
|
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:
|
|
54
54
|
|
|
@@ -57,7 +57,7 @@ The parity set is **template-driven**: every column-0 YAML key under `## Session
|
|
|
57
57
|
- `templates-first` (Pattern 3 / #519) — gh/glab template-read enforcement hook
|
|
58
58
|
- `verification-auto-fix` (Pattern 4 / #521) — opt-in auto-fix retry loop after Quality-Gate fail
|
|
59
59
|
|
|
60
|
-
A local CLAUDE.md / AGENTS.md that omits any of these
|
|
60
|
+
A local CLAUDE.md / AGENTS.md that omits any of these surfaces a `session-config-parity` **note** — they are opt-in-baseline-only keys, so omitting them never trips `mode: hard`, and never blocks `autonomous-gated` skill-evolution's `runConfigValidationGate()` (which runs the checker with `--mode hard` and reads only `errors[]`).
|
|
61
61
|
|
|
62
62
|
Check 7 (issue #600) is the **only** check that intentionally reads BOTH instruction files rather than the single alias-resolved one. The alias rule (CLAUDE.md wins ties, AGENTS.md is the Codex alias) means `resolveInstructionFile()` picks exactly one — so a repo carrying both files can silently let `AGENTS.md` drift out of sync with `CLAUDE.md`. A sibling project ran for weeks with a correct `vault-integration.vault-dir` in `CLAUDE.md` and a dead path in `AGENTS.md`. Check 7 reads `vault-integration.vault-dir` from each file (reusing the `_parseVaultIntegration` parser from `scripts/lib/config/vault-integration.mjs` — no hand-rolled YAML) and flags a `vault-dir-parity` error when the two values diverge (the error is attributed to `AGENTS.md`, the secondary alias, and names both values). The check skips gracefully when only one instruction file is present (nothing to compare), when neither file declares a `vault-integration:` block, or when explicitly disabled via `--skip-vault-dir-parity`. Two files that both omit `vault-dir` (both unset) agree and pass.
|
|
63
63
|
|
|
@@ -65,11 +65,11 @@ Check 7 (issue #600) is the **only** check that intentionally reads BOTH instruc
|
|
|
65
65
|
|
|
66
66
|
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.
|
|
67
67
|
|
|
68
|
-
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. Six probes: **(1) paths-presence** — 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. 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`.
|
|
68
|
+
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. Six 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. 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`.
|
|
69
69
|
|
|
70
70
|
**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.
|
|
71
71
|
|
|
72
|
-
Check 10 (`docs-parity`, issue #780) validates the public docs surface against live repository state via three sub-checks, all reported under the single `docs-parity` check id: **(a) count-claims** — `docs/components.md`'s own heading counts ("## Skills (N user-facing)", "## Commands (N)", "## Agents (N typed sub-agents)", "## Hook event types (N)") are compared against the SAME actual on-disk derivation the surface-count family (Check 5) uses (`countSkills
|
|
72
|
+
Check 10 (`docs-parity`, issue #780) validates the public docs surface against live repository state via three sub-checks, all reported under the single `docs-parity` check id: **(a) count-claims** — `docs/components.md`'s own heading counts ("## Skills (N user-facing)", "## Commands (N)", "## Agents (N typed sub-agents)", "## Hook event types (N)") are compared against the SAME actual on-disk derivation the surface-count family (Check 5) uses (`countSkills`/`countSlashCommands`/`countAgents`/`readHookCounts`) — NOT Check 5's `claimRe` regexes, which are tuned for CLAUDE.md/README prose phrasing and verifiably do not match `components.md`'s own heading convention; a dedicated set of regexes tailored to the doc's actual authored structure is used instead. **(b) config-block-parity** — top-level Session Config keys documented in `docs/session-config-template.md` (opt-in baseline, via the Check 6 'last'-occurrence extractor) are compared against `docs/session-config-reference.md`; a key is considered documented in the reference when it appears in a `yaml` fence (any indent), a `##`/`###`/`####` heading, or the first cell of a markdown table row (`` | `key-name` | ... ``) — the reference's dominant documentation shape is one table row per field, so a naive fence-only extraction under-counts what it actually documents. Missing keys surface as `docs-parity` errors attributed to `docs/session-config-reference.md`. **(c) metrics-path-liveness** — any `.claude/metrics/` path reference (stale; canonical is `.orchestrator/metrics/`) found in root `docs/*.md` or `docs/examples/*.md` surfaces as an error at its exact file/line. The whole check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `docs/components.md` is absent, or explicitly via `--skip-docs-parity`.
|
|
73
73
|
|
|
74
74
|
## Files
|
|
75
75
|
|
|
@@ -133,7 +133,7 @@ Environment:
|
|
|
133
133
|
}
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
`notes[]` is the THIRD output category, alongside `errors[]` and `warnings[]`: a finding that is reported but asks for no action. It never affects `status` or the exit code, and it is always present (empty array when nothing was noted, including on the short-circuit "no scope files matched" path).
|
|
136
|
+
`notes[]` is the THIRD output category, alongside `errors[]` and `warnings[]`: a finding that is reported but asks for no action. It never affects `status` or the exit code, and it is always present (empty array when nothing was noted, including on the short-circuit "no scope files matched" path). Producers are Check 6's `opt-in-gap` probe and Check 9's `fleet-intent-glob` probe. Opt-in-gap notes carry `check: "session-config-parity"`, `probe: "opt-in-gap"`, the instruction file and line, a message, and the omitted key in `extracted`; they have no `source` field.
|
|
137
137
|
|
|
138
138
|
The `resolved_path` / `resolved_kind` pair surfaces the alias resolution outcome (issue #33 AC2) so users on either platform can audit which instruction file the checker scanned. `kind: 'claude'` for `CLAUDE.md`, `kind: 'agents'` for `AGENTS.md`, `null` when neither was found.
|
|
139
139
|
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
* Reuses `_parseVaultIntegration` from scripts/lib/config/ for Check 7;
|
|
26
26
|
* reuses `parseGlobsFrontmatter` from scripts/lib/rule-loader.mjs for Check 9's
|
|
27
27
|
* glob extraction (mirrors the same picomatch-with-inline-fallback resolution
|
|
28
|
-
* rule-loader.mjs uses)
|
|
28
|
+
* rule-loader.mjs uses) and its line-1-only frontmatter rule (#1449: a block
|
|
29
|
+
* with anything before its opening `---` is not frontmatter, exactly as Claude
|
|
30
|
+
* Code reads it); otherwise pure Node stdlib.
|
|
29
31
|
*/
|
|
30
32
|
|
|
31
33
|
import { readFileSync, readdirSync, existsSync, statSync, lstatSync, realpathSync } from 'node:fs';
|
|
@@ -36,7 +38,7 @@ import { resolveInstructionFile } from '../../scripts/lib/common.mjs';
|
|
|
36
38
|
import { _parseVaultIntegration } from '../../scripts/lib/config/vault-integration.mjs';
|
|
37
39
|
import { _parseDriftCheck } from '../../scripts/lib/config/drift-check.mjs';
|
|
38
40
|
import { isSessionConfigHeading } from '../../scripts/lib/config/section-extractor.mjs';
|
|
39
|
-
import { parseGlobsFrontmatter } from '../../scripts/lib/rule-loader.mjs';
|
|
41
|
+
import { FRONTMATTER_NOT_AT_TOP, parseGlobsFrontmatter } from '../../scripts/lib/rule-loader.mjs';
|
|
40
42
|
import { resolveRepoSpec } from '../../scripts/lib/vcs-repo-spec.mjs';
|
|
41
43
|
import { userInvocableSkills } from '../../scripts/lib/user-invocable-skills.mjs';
|
|
42
44
|
|
|
@@ -573,7 +575,11 @@ function lookupIssueState(iid, repo, cache, vaultDir) {
|
|
|
573
575
|
// rule-loader.mjs's parseGlobsFrontmatter (the `paths:` alias, issue
|
|
574
576
|
// #795) fails to recognise — a genuine parse mismatch. A well-formed
|
|
575
577
|
// `paths:`-only rule is NOT flagged: since #795, `paths:` is a full
|
|
576
|
-
// alias for `globs:` and loads correctly SCOPED (#840).
|
|
578
|
+
// alias for `globs:` and loads correctly SCOPED (#840). Also an error
|
|
579
|
+
// (#1449): a frontmatter block that does not start on line 1 (a
|
|
580
|
+
// provenance comment, a blank line or any text before the opening
|
|
581
|
+
// `---`) — parseGlobsFrontmatter reports it as `frontmatter-not-at-top`,
|
|
582
|
+
// Claude Code ignores such a block, and the rule loads ALWAYS-ON.
|
|
577
583
|
// 2. cited-but-missing → errors[]: (a) `.claude/rules/<name>.md` citations in
|
|
578
584
|
// CLAUDE.md/AGENTS.md that don't exist on disk; (b) bare `<name>.md`
|
|
579
585
|
// tokens in a rule's own `## See Also` footer that don't exist on disk
|
|
@@ -674,21 +680,12 @@ function listTrackedFiles(vaultDir) {
|
|
|
674
680
|
}
|
|
675
681
|
}
|
|
676
682
|
|
|
677
|
-
const RULE_HEADER_LINE_RE = /^[ \t]*(?:<!--.*-->)?[ \t]*$/;
|
|
678
|
-
|
|
679
|
-
function stripLeadingRuleHeaderLines(content) {
|
|
680
|
-
const lines = content.split(/\r?\n/);
|
|
681
|
-
let idx = 0;
|
|
682
|
-
while (idx < lines.length && RULE_HEADER_LINE_RE.test(lines[idx])) idx++;
|
|
683
|
-
return lines.slice(idx).join('\n');
|
|
684
|
-
}
|
|
685
|
-
|
|
686
683
|
/** Extracts the raw frontmatter block body (text between the `---` delimiters),
|
|
687
|
-
* or null when the file has no frontmatter block.
|
|
688
|
-
*
|
|
689
|
-
*
|
|
684
|
+
* or null when the file has no frontmatter block. Only a block whose opening
|
|
685
|
+
* `---` is line 1 counts (#1449) — the rule rule-loader.mjs and Claude Code
|
|
686
|
+
* apply; a displaced block is body text, reported by probe 1 instead. */
|
|
690
687
|
function extractFrontmatterBlockBody(content) {
|
|
691
|
-
const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(
|
|
688
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
|
|
692
689
|
return m ? m[1] : null;
|
|
693
690
|
}
|
|
694
691
|
|
|
@@ -1173,7 +1170,7 @@ function main() {
|
|
|
1173
1170
|
// Minimal inline frontmatter extractor for auto-generated + learning-key.
|
|
1174
1171
|
// Reads the opening --- ... --- block from a markdown file.
|
|
1175
1172
|
function extractFrontmatterFields(mdContent) {
|
|
1176
|
-
const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(
|
|
1173
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(mdContent);
|
|
1177
1174
|
if (!m) return { autoGenerated: false, learningKey: null, expiresAt: null, evidenceDigest: null };
|
|
1178
1175
|
const block = m[1];
|
|
1179
1176
|
const autoGenM = /^auto-generated:\s*(.+)$/m.exec(block);
|
|
@@ -1370,10 +1367,21 @@ function main() {
|
|
|
1370
1367
|
// parseGlobsFrontmatter failed to recognise it (globs === null) —
|
|
1371
1368
|
// a genuine parse mismatch between this probe's textual detection
|
|
1372
1369
|
// and the loader's actual behaviour, not routine `paths:` usage.
|
|
1370
|
+
// #1449: the same parser also reports a frontmatter block that does
|
|
1371
|
+
// not start on line 1. Claude Code ignores that block, so whatever
|
|
1372
|
+
// key it carries (globs:, paths:, tier:, …) has no effect and the
|
|
1373
|
+
// rule loads ALWAYS-ON — an error of its own, reported instead of
|
|
1374
|
+
// the paths: mismatch (fmBody is null for such a file anyway).
|
|
1373
1375
|
let parsed;
|
|
1374
1376
|
try { parsed = parseGlobsFrontmatter(content); } catch { parsed = { globs: null, meta: {} }; }
|
|
1375
1377
|
const fmBody = extractFrontmatterBlockBody(content);
|
|
1376
|
-
if (
|
|
1378
|
+
if (parsed.defect === FRONTMATTER_NOT_AT_TOP) {
|
|
1379
|
+
errors.push({
|
|
1380
|
+
check: 'rule-scoping', file: relPath, line: 1,
|
|
1381
|
+
message: `${relPath}: frontmatter is not at line 1 (text before the opening ---) — Claude Code ignores it, the rule loads ALWAYS-ON; move the frontmatter to line 1 and put the provenance comment after the closing ---`,
|
|
1382
|
+
extracted: FRONTMATTER_NOT_AT_TOP,
|
|
1383
|
+
});
|
|
1384
|
+
} else if (fmBody && /^paths:/m.test(fmBody) && parsed.globs === null) {
|
|
1377
1385
|
errors.push({
|
|
1378
1386
|
check: 'rule-scoping', file: relPath, line: 1,
|
|
1379
1387
|
message: `Rule frontmatter declares 'paths:' but rule-loader.mjs's parseGlobsFrontmatter did not recognise it — the rule may silently load ALWAYS-ON. Verify the paths:/globs: frontmatter syntax.`,
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
* 2. `.claude/rules/*.md` file count (non-recursive) vs. "N rule files" claims.
|
|
25
25
|
* 3. `skills/` user-facing directory count (excludes `_shared/`) vs.
|
|
26
26
|
* "N user-facing skills" / "N skills" claims.
|
|
27
|
-
* 4. `commands/*.md`
|
|
27
|
+
* 4. Slash-command count (`commands/*.md` ∪ skills with `user-invocable: true`)
|
|
28
|
+
* vs. "N slash-commands" / "N commands" claims.
|
|
28
29
|
*
|
|
29
30
|
* Scope exclusions: CHANGELOG.md, docs/adr/, docs/prd/, docs/retro/ are never
|
|
30
31
|
* scanned — same principle as the docs-staleness probe (historical/immutable
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
|
|
46
47
|
import { existsSync, readdirSync, readFileSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
47
48
|
import { join } from 'node:path';
|
|
49
|
+
import { slashCommandNames } from '../../../scripts/lib/user-invocable-skills.mjs';
|
|
48
50
|
|
|
49
51
|
// ---------------------------------------------------------------------------
|
|
50
52
|
// Doc source sets (deliberately small and hand-curated)
|
|
@@ -101,12 +103,16 @@ function countUserFacingSkills(root) {
|
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
// Slash commands = `commands/*.md` ∪ skills with an explicit
|
|
107
|
+
// `user-invocable: true` (the #1370 commands→skills fold) — the shared census
|
|
108
|
+
// in user-invocable-skills.mjs, not a second copy. Counting `commands/` alone
|
|
109
|
+
// reported 2 against the real 27 on every run (#1506 pt 7). `null` (entry
|
|
110
|
+
// skipped) only when the repo has neither source.
|
|
104
111
|
function countCommands(root) {
|
|
105
|
-
const dir = join(root, 'commands');
|
|
106
|
-
if (!existsSync(dir)) return null;
|
|
107
112
|
try {
|
|
108
|
-
const
|
|
109
|
-
|
|
113
|
+
const names = slashCommandNames(root);
|
|
114
|
+
if (names.length === 0 && !existsSync(join(root, 'commands'))) return null;
|
|
115
|
+
return names.length;
|
|
110
116
|
} catch {
|
|
111
117
|
return null;
|
|
112
118
|
}
|
|
@@ -180,7 +186,7 @@ const REGISTRY = [
|
|
|
180
186
|
},
|
|
181
187
|
{
|
|
182
188
|
id: 'commands-count',
|
|
183
|
-
label: 'commands/*.md
|
|
189
|
+
label: 'slash-command count (commands/*.md + user-invocable skills)',
|
|
184
190
|
sources: SOURCE_SET_INVENTORY,
|
|
185
191
|
severity: 'medium',
|
|
186
192
|
getActual: countCommands,
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import { existsSync, readFileSync, readdirSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
26
26
|
import { join } from 'node:path';
|
|
27
|
+
import { expandTilde } from '../../../scripts/lib/common.mjs';
|
|
27
28
|
|
|
28
29
|
// ---------------------------------------------------------------------------
|
|
29
30
|
// Constants
|
|
@@ -73,9 +74,12 @@ function parseFrontmatter(text) {
|
|
|
73
74
|
// Helpers
|
|
74
75
|
// ---------------------------------------------------------------------------
|
|
75
76
|
|
|
77
|
+
// #1506 pt 6: the committed value is `~/Projects/vault`; unexpanded it resolved
|
|
78
|
+
// against the cwd (`<cwd>/~/Projects/vault`), where a stray `~` directory
|
|
79
|
+
// produced a false all-clear (0 stale narratives instead of 33).
|
|
76
80
|
function resolveVaultDir(projectRoot, config) {
|
|
77
|
-
if (config?.['vault-integration']?.['vault-dir']) return config['vault-integration']['vault-dir'];
|
|
78
|
-
if (process.env.VAULT_DIR) return process.env.VAULT_DIR;
|
|
81
|
+
if (config?.['vault-integration']?.['vault-dir']) return expandTilde(config['vault-integration']['vault-dir']);
|
|
82
|
+
if (process.env.VAULT_DIR) return expandTilde(process.env.VAULT_DIR);
|
|
79
83
|
return null;
|
|
80
84
|
}
|
|
81
85
|
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
import { existsSync, readFileSync, readdirSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
40
40
|
import { join } from 'node:path';
|
|
41
|
+
import { expandTilde } from '../../../scripts/lib/common.mjs';
|
|
41
42
|
|
|
42
43
|
// ---------------------------------------------------------------------------
|
|
43
44
|
// Minimal YAML frontmatter parser — top-level scalar fields only (<30 lines).
|
|
@@ -112,13 +113,18 @@ export async function runProbe(projectRoot, config) {
|
|
|
112
113
|
try {
|
|
113
114
|
// --- Config validation (early exit paths) ---
|
|
114
115
|
|
|
115
|
-
const
|
|
116
|
-
if (!
|
|
116
|
+
const configuredVaultDir = config?.['vault-integration']?.['vault-dir'];
|
|
117
|
+
if (!configuredVaultDir) {
|
|
117
118
|
return makeSkip('vault-dir not configured');
|
|
118
119
|
}
|
|
120
|
+
// #1506 pt 6: the committed value is `~/Projects/vault`; unexpanded it
|
|
121
|
+
// resolved against the cwd (`<cwd>/~/Projects/vault`), where a stray `~`
|
|
122
|
+
// directory produced a false all-clear (0 projects scanned instead of 45).
|
|
123
|
+
const vaultDir = expandTilde(configuredVaultDir);
|
|
119
124
|
|
|
120
125
|
if (!existsSync(vaultDir)) {
|
|
121
|
-
|
|
126
|
+
// The configured spelling, not the expanded one: no home dir in the output.
|
|
127
|
+
return makeSkip(`vault-dir does not exist: ${configuredVaultDir}`);
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
const projectsDir = join(vaultDir, '01-projects');
|
|
@@ -177,9 +177,15 @@ No Docs task generated for this audience.
|
|
|
177
177
|
documents what the docs-writer MUST do when executing a dispatched Docs task.
|
|
178
178
|
|
|
179
179
|
Before writing any documentation, the docs-writer MUST read and ingest the actual
|
|
180
|
-
source material provided in the task prompt. The canonical four source types are
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
source material provided in the task prompt. The canonical four source types are
|
|
181
|
+
below. `$SESSION_RANGE` is ONE complete diff range: inside a wave run it is
|
|
182
|
+
`"$SESSION_START_REF..HEAD"` (the SHA wave-executor pins at Pre-Wave 1a); at
|
|
183
|
+
session end it is what the accessor in `skills/session-end/plan-verification.md`
|
|
184
|
+
§ SESSION_START_REF accessor exports — the pinned SHA, else the resolved
|
|
185
|
+
`<base>...HEAD`, never a hard-coded `origin/main`. Diffs take the range; the log
|
|
186
|
+
keeps the two-dot `$SESSION_START_REF..HEAD`, which is the correct form for `git log`.
|
|
187
|
+
|
|
188
|
+
1. **diff** — `git diff "$SESSION_RANGE"` — the verbatim diff of all changes
|
|
183
189
|
made during this session. This is the primary authoritative source for what changed.
|
|
184
190
|
2. **git-log** — `git log $SESSION_START_REF..HEAD --format="%H %s%n%b"` — commit
|
|
185
191
|
messages and associated bodies. Secondary context for all audiences; use to
|
|
@@ -284,9 +290,10 @@ The docs-writer MUST emit a brief structured output summary:
|
|
|
284
290
|
1. Collect every task classified as `Docs` in the plan.
|
|
285
291
|
2. For each task, resolve the expected `file-pattern` target and check for a diff:
|
|
286
292
|
```bash
|
|
287
|
-
git diff --name-only $
|
|
293
|
+
git diff --name-only "$SESSION_RANGE"
|
|
288
294
|
```
|
|
289
|
-
|
|
295
|
+
(`$SESSION_RANGE` from the session-end accessor above; an empty range is a skip
|
|
296
|
+
with a named reason, never "no docs changed".) Match the output against the task's file-pattern using glob matching.
|
|
290
297
|
3. Classify gaps by severity:
|
|
291
298
|
- **missing diff** — no matching file was changed; the task did not run or produced
|
|
292
299
|
no output.
|
|
@@ -23,14 +23,16 @@ traceable source gets `<!-- REVIEW: source needed -->` — the agent NEVER inven
|
|
|
23
23
|
|
|
24
24
|
| Source | Command / Path | Primary audience(s) | Notes |
|
|
25
25
|
|--------|---------------|---------------------|-------|
|
|
26
|
-
| **diff** | `git diff $
|
|
26
|
+
| **diff** | `git diff "$SESSION_RANGE"` | Dev, Vault | Authoritative for what changed and when. Preferred primary source for Dev. |
|
|
27
27
|
| **git-log** | `git log $SESSION_START_REF..HEAD --format="%H %s%n%b"` | All | Secondary context; use to reconstruct the "why" when diffs alone are ambiguous. |
|
|
28
28
|
| **session-memory** | `~/.claude/projects/<project>/memory/session-*.md`, `.orchestrator/` outputs | Vault, User | Primary for Vault narratives (status updates, decisions made in conversation). Also primary for User tasks designed interactively. |
|
|
29
29
|
| **affected-files** | Files in wave-scope `allowedPaths` / coordinator `affected-files` context block | User, Dev | Primary for understanding updated interfaces, config schemas, or module structures. |
|
|
30
30
|
|
|
31
|
+
`$SESSION_RANGE` is one complete diff range — `"$SESSION_START_REF..HEAD"` inside a wave run, the session-end accessor's export at close (`../session-end/plan-verification.md` § SESSION_START_REF accessor; never a hard-coded `origin/main`). The log keeps the two-dot `$SESSION_START_REF..HEAD`, the correct form for `git log`.
|
|
32
|
+
|
|
31
33
|
**Detailed source descriptions:**
|
|
32
34
|
|
|
33
|
-
1. **diff** — Direct code changes from `git diff $
|
|
35
|
+
1. **diff** — Direct code changes from `git diff "$SESSION_RANGE"`. Authoritative
|
|
34
36
|
for Dev narratives (what changed and why) and Vault decisions (what was decided, when).
|
|
35
37
|
Preferred primary source for Dev audience tasks.
|
|
36
38
|
2. **git-log** — Commit messages and associated PR/MR bodies from
|
|
@@ -90,7 +92,7 @@ Trigger: New `coordinator-snapshot.mjs` module added; CWD-drift guard introduced
|
|
|
90
92
|
## Allowed Sources
|
|
91
93
|
Only these four sources are permitted. Do not use general knowledge.
|
|
92
94
|
|
|
93
|
-
- diff: `git diff $
|
|
95
|
+
- diff: `git diff "$SESSION_RANGE"` (provided below)
|
|
94
96
|
- git-log: `git log $SESSION_START_REF..HEAD --format="%H %s%n%b"` (provided below)
|
|
95
97
|
- session-memory: wave summaries and agent outputs from
|
|
96
98
|
~/.claude/projects/<project>/memory/session-*.md and .orchestrator/ (provided below)
|
package/skills/evolve/SKILL.md
CHANGED
|
@@ -171,18 +171,27 @@ If user selects "Confidence ändern", "Ablauf verlängern", or "Delete specific
|
|
|
171
171
|
|
|
172
172
|
Use the same archive-safe pipeline as Phase 3, Step 3.5 — **never** a hand-rolled `>` rewrite (#1017):
|
|
173
173
|
|
|
174
|
-
1.
|
|
174
|
+
1. Snapshot the store into the sidecar exactly as Phase 3, Step 3.5(1) does (`--prune --snapshot`,
|
|
175
|
+
with the same `--file` / `--archive` the apply will use) and work on the sidecar, never on a
|
|
176
|
+
separate read of `learnings.jsonl` (#1486). A snapshot exit `1` (a malformed store line, or a
|
|
177
|
+
PATH that would replace a ledger) wrote nothing: surface it and stop
|
|
175
178
|
2. Apply the selected operation to selected learnings:
|
|
176
|
-
- **Boost:** +0.15 confidence (cap 1.0), reset expires_at to
|
|
179
|
+
- **Boost:** +0.15 confidence (cap 1.0), reset expires_at to `deriveExpiresAt(now, type)` (per-type TTL, `scripts/lib/learnings/schema.mjs`)
|
|
177
180
|
- **Reduce:** -0.2 confidence
|
|
178
181
|
- **Delete:** omit the selected entries from the next generation — do NOT delete them by hand.
|
|
179
182
|
`pruneLearnings()` detects every **record** that left the store — reconciled by `id`, or by a
|
|
180
183
|
content fingerprint when a record carries no usable `id` — and archives it with
|
|
181
184
|
`_archive_reason: "pruned"`, so a `learning-id` referenced by a rendered rule stays resolvable.
|
|
182
|
-
- **Extend:** reset expires_at to
|
|
185
|
+
- **Extend:** reset expires_at to `deriveExpiresAt(now, type)` — `learning-expiry-days` is parsed but read by no script (`skills/_shared/config-reading.md` § Learning Expiry Semantics)
|
|
183
186
|
3. Steps 3–5 of the old prose (prune / consolidate / rewrite) are `pruneLearnings()` — run the
|
|
184
187
|
**exact** Step 3.5(5) invocation, writing the post-operation entry set to the `--entries`
|
|
185
|
-
sidecar.
|
|
188
|
+
sidecar below its line-1 `_store_generation` header. Exit `1` or `2`: surface the error and
|
|
189
|
+
stop (`1` includes a sidecar snapshotted from another store than `--file`). Exit `3` means the
|
|
190
|
+
store changed after the snapshot and nothing was written: snapshot into a FRESH path,
|
|
191
|
+
re-apply the selected operations to the records in that file, and apply it once more — a
|
|
192
|
+
second consecutive `3`: stop and report. Never move the fresh header onto the old sidecar's
|
|
193
|
+
records, whose missing peer change the prune would archive `pruned` (Step 3.5(5) carries the
|
|
194
|
+
full exit-code rule). It prunes
|
|
186
195
|
(`expires_at` < now → `expired`; `confidence <= 0.0` → `pruned`), consolidates duplicates
|
|
187
196
|
(same `type` + non-empty `subject`, highest confidence wins, loser archived `superseded` with
|
|
188
197
|
`_superseded_by`; null-subject entries preserved individually per #284), and rewrites through
|
|
@@ -61,10 +61,9 @@ For each of the 9 built-in analyzer learning types, apply these heuristics.
|
|
|
61
61
|
|
|
62
62
|
- Compare `total_agents` and `total_waves` across session types
|
|
63
63
|
- Calculate average agents per wave for each session type. Read a wave's agent count defensively — older records use other keys: `agent_count`, else `agents` when it is a number, else the length of `agents` when it is an array (of descriptions), else `agents_dispatched`. A record with `total_waves: 0` contributes no per-wave ratio — skip it rather than divide by zero. Exclude coordinator-direct waves (`coordinator_direct: true`, which dispatched no agents) from the ratio. In particular, a record whose waves are all coordinator-direct `Housekeeping` waves (predicate `isCoordinatorDirectHousekeeping` in `scripts/lib/session-schema/filters.mjs`, the session-end writer shape since #1321) contributes no per-wave ratio, same as `total_waves: 0`. Counting it would log a false 0.0 agents-per-wave observation.
|
|
64
|
-
- **Group by `(session_type, session_profile)` (#1247), never by `session_type` alone.** `ultradeep` is a PROFILE over `session_type: deep` (7 waves vs. deep's 4-5, STATE.md frontmatter key `session-profile` — see `scripts/lib/state-md.mjs`), not a distinct type, so a bare `session_type` grouping folds a 7-wave ultradeep session and a 5-wave deep session into the same row and silently averages their agents-per-wave
|
|
64
|
+
- **Group by `(session_type, session_profile)` (#1247), never by `session_type` alone.** `ultradeep` is a PROFILE over `session_type: deep` (7 waves vs. deep's 4-5, STATE.md frontmatter key `session-profile` — see `scripts/lib/state-md.mjs`), not a distinct type, so a bare `session_type` grouping folds a 7-wave ultradeep session and a 5-wave deep session into the same row and silently averages their agents-per-wave numbers together.
|
|
65
65
|
- Subject = `sizingSubject({ session_type, session_profile })` from `scripts/lib/learnings/sizing-subject.mjs` — e.g. `deep-session-sizing` (no profile, byte-identical to the pre-#1247 literal), `deep-ultradeep-session-sizing` (profile `ultradeep`), or `feature-session-sizing`. Always derive via this helper; do not hand-concatenate the subject string.
|
|
66
66
|
- Insight = "Deep sessions average X agents across Y waves" or "Ultradeep sessions average X agents across Y waves" (name the profile in the insight whenever `session_profile` is present, so the two rows read as distinct sizing observations, not variants of one sentence)
|
|
67
|
-
- **Over-delivery ratio aggregation (#730/H4, #794.7; keyed by profile since #1247):** compute the MEDIAN of `waves[].over_delivery_ratio` across the last ~5 `sessions.jsonl` records of the same `(session_type, session_profile)` pair, filtered to waves whose `role` is not `Discovery`/`Finalization` and which carry the field (skip records lacking the field — pre-#730; also skip Discovery/Finalization waves, whose planned set is empty by design). This exclusion clause is intentionally identical to `skills/session-plan/SKILL.md` Step 0.5 "Over-delivery sizing" — keep the two wordings in sync on edit. Fold the median into this candidate's `insight`/`evidence` fields — e.g. `evidence`: `"median_over_delivery_ratio: 1.4 (n=12 waves, session_type=deep, session_profile=ultradeep)"` — so `session-plan` Step 0.5 can read the ratio from the `effective-sizing` learning first, falling back to its own direct `sessions.jsonl` scan only when no such learning exists.
|
|
68
67
|
|
|
69
68
|
#### 3. recurring-issue (type: `recurring-issue`)
|
|
70
69
|
|
|
@@ -209,7 +208,7 @@ If user selects "Skip all" or selects nothing, abort gracefully: "No learnings s
|
|
|
209
208
|
|
|
210
209
|
For confirmed learnings, use atomic rewrite strategy:
|
|
211
210
|
|
|
212
|
-
1.
|
|
211
|
+
1. Snapshot the store into the sidecar: `node scripts/sweep-expired-learnings.mjs --prune --snapshot .orchestrator/tmp/learnings-next.jsonl`. It writes every record of `.orchestrator/metrics/learnings.jsonl` to that file behind a line-1 `{"_store_generation": "sha256:…", "_store_path": "…"}` header naming the exact store state and the store they came from. Read the SIDECAR, not the store, as your starting set — step 5 explains why. On a non-default store, pass the SAME `--file` / `--archive` here as to the step-5 apply: the apply refuses (exit `1`) a sidecar snapshotted from another store. A store holding a malformed (unparseable) line does NOT stop the snapshot: it prints a stderr WARN naming the line numbers and adds a `malformed` count to its summary. The sidecar carries records only, and the step-5 apply keeps the store's malformed lines verbatim at the end of the rewrite, never archived — report the count to the operator; never hand-edit the store to remove them. The one sanctioned repair is `node scripts/sweep-expired-learnings.mjs --drop-malformed --line N [--line M …]`: a dry run by default, printing each line's preview, any readable record it still carries (`bom` / `embedded_record`) and the store generation; only after the operator confirmed those previews, `--drop-malformed --line N --apply --generation <TOKEN> --repo-root "$(pwd)"` removes the lines (backup `<stem>.pre-drop-malformed.jsonl.bak-<ISO>` beside the store). A line still holding a readable record needs `--accept-embedded`; a store that changed since the dry run exits `3`. If the snapshot holds zero records, check `<state-dir>/metrics/learnings.jsonl` as a legacy fallback and add its records below line 1; they are migrated to the v2 path on write (step 5).
|
|
213
212
|
2. Apply confidence updates for confirmed existing learnings:
|
|
214
213
|
- Increment confidence by +0.15
|
|
215
214
|
- Cap at 1.0
|
|
@@ -236,9 +235,22 @@ For confirmed learnings, use atomic rewrite strategy:
|
|
|
236
235
|
deleted 11 of 13 `learning-id` provenance targets referenced by rendered `.claude/rules/*.md`.
|
|
237
236
|
Do not hand-roll a `jq | ... > learnings.jsonl` pass; it bypasses every #721 safety net.
|
|
238
237
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
Turn the step-1 sidecar `.orchestrator/tmp/learnings-next.jsonl` into the full
|
|
239
|
+
next-generation entry set (existing entries **with** the step-2/3 confidence updates,
|
|
240
|
+
**plus** the step-4 new learnings) **via the Write or Edit tool** (not a shell `>` redirect —
|
|
241
|
+
the destructive-command guard blocks it), **keeping line 1, the `_store_generation` header,
|
|
242
|
+
unchanged and first**. The sidecar lives under `.orchestrator/tmp/`, never
|
|
243
|
+
`.orchestrator/metrics/`, because `tmp/` is outside
|
|
244
|
+
the ledger guard's `ledger-delete-protected` denylist — a metrics-dir sidecar made the
|
|
245
|
+
cleanup `rm` below a blocked command (#1453).
|
|
246
|
+
|
|
247
|
+
**Snapshot → token → apply refuses on drift (#1486).** Minutes pass between step 1 and the
|
|
248
|
+
call below, and a peer session may append to the store meanwhile. Such a record is on disk
|
|
249
|
+
but absent from your sidecar, so the prune would archive it `pruned` — a delete nobody
|
|
250
|
+
decided, which the store lock cannot prevent because your read happened before it, in
|
|
251
|
+
another process. So the prune re-reads the store under its lock and compares that
|
|
252
|
+
generation with the header: any difference writes nothing and exits `3`. A sidecar without
|
|
253
|
+
the header exits `1` — the header is mandatory, never optional. Then invoke the
|
|
242
254
|
`--prune` subcommand of the sweep CLI. **This call is also `/evolve`'s ONLY
|
|
243
255
|
`orchestrator.evolve.completed` success emit (#1206)** — export `N` (Step 3.5(4)'s
|
|
244
256
|
new-learnings count), `M` (Step 3.5(2)'s reinforced-existing count) and `DURATION_MS`
|
|
@@ -247,14 +259,15 @@ For confirmed learnings, use atomic rewrite strategy:
|
|
|
247
259
|
an argument error:
|
|
248
260
|
|
|
249
261
|
```bash
|
|
250
|
-
NEXT=".orchestrator/
|
|
262
|
+
NEXT=".orchestrator/tmp/learnings-next.jsonl" # written by the step above
|
|
251
263
|
node scripts/sweep-expired-learnings.mjs --prune --apply --json --entries "$NEXT" \
|
|
252
264
|
--appended "${N:-0}" --boosted "${M:-0}" --duration-ms "${DURATION_MS:-0}" \
|
|
253
265
|
--repo-root "$(pwd)" && rm -f "$NEXT"
|
|
254
266
|
```
|
|
255
267
|
|
|
256
268
|
`--file` / `--archive` default to the canonical store + archive paths — pass them only when
|
|
257
|
-
operating on a non-default pair
|
|
269
|
+
operating on a non-default pair, and then pass the same pair to the step-1 `--snapshot` too.
|
|
270
|
+
The command prints ONE JSON line; capture it as `$PRUNE` and
|
|
258
271
|
report its `{scanned, kept, archived, byReason}` in the final summary — `$PRUNE.archived` is
|
|
259
272
|
also the `pruned` counter the emit above just wrote, so there is nothing left to compute for
|
|
260
273
|
the telemetry after this line. Preview first with `--prune --dry-run --json` (same counts,
|
|
@@ -269,12 +282,22 @@ For confirmed learnings, use atomic rewrite strategy:
|
|
|
269
282
|
> independently of the write it reported on. Do not re-inline either, and do not hand-roll a
|
|
270
283
|
> `jq | ... > learnings.jsonl` pass — that bypasses every #721 safety net.
|
|
271
284
|
|
|
272
|
-
**Exit codes are the no-op rule.** `0` = applied (or a clean no-op). `1`
|
|
273
|
-
sidecar is absent, carries a malformed line,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
285
|
+
**Exit codes are the no-op rule.** `0` = applied (or a clean no-op). On `1` and `2`, surface
|
|
286
|
+
the error and stop. `1` = input error: the sidecar is absent, carries a malformed line, holds
|
|
287
|
+
no records, lacks its line-1 `_store_generation` header, or was snapshotted from another store
|
|
288
|
+
than this call's `--file` — the store and the archive were **not touched**. `2` = the prune
|
|
289
|
+
itself failed inside the lib. `3` =
|
|
290
|
+
`store-generation-mismatch`: the store changed after step 1 and nothing was written. This one
|
|
291
|
+
is a retry, not a stop: snapshot into a FRESH path
|
|
292
|
+
(`--prune --snapshot .orchestrator/tmp/learnings-next-retry.jsonl`, with the same `--file` /
|
|
293
|
+
`--archive` as the apply), re-apply this run's
|
|
294
|
+
decisions to the records in THAT file — they now include the peer's change — and run the block
|
|
295
|
+
above with its `NEXT` set to the fresh path. Retry once: a second consecutive `3` means the
|
|
296
|
+
store keeps moving under you — stop and report it. Never move the fresh header onto the old
|
|
297
|
+
sidecar's records: they lack exactly the peer change the header exists to protect, so the
|
|
298
|
+
prune would archive it `pruned` (the error prints no token for that reason). On every exit,
|
|
299
|
+
never retry with a shell rewrite and never delete `$NEXT` — the `&&` above withholds the `rm`,
|
|
300
|
+
so the assembled generation survives as the record of this run's decisions.
|
|
278
301
|
|
|
279
302
|
`pruneLearnings()` — the function the subcommand calls — performs steps 6 + 7 + 8 mechanically
|
|
280
303
|
and archives **every** record that
|
|
@@ -301,21 +324,22 @@ For confirmed learnings, use atomic rewrite strategy:
|
|
|
301
324
|
old "read back the first line to confirm valid JSON" check is redundant here: `rewriteLearnings()`
|
|
302
325
|
round-trip-validates EVERY line before any byte reaches disk (#662), and the `malformed` guard
|
|
303
326
|
above rejects an unparseable sidecar before the store is touched at all.
|
|
304
|
-
6. **Vault mirror (conditional):** Check `$CONFIG."vault-integration".enabled` via jq. If the field is missing or `false`, skip this step entirely — skill behavior is unchanged.
|
|
327
|
+
6. **Vault mirror (conditional):** Check `$CONFIG."vault-integration".enabled` via jq. If `$CONFIG."vault-integration"."host-override"` is set and the integration is off (`enabled` is `false` or `mode` is `off`), report `vault-mirror: bewusst aus auf diesem Host (<host-override>)` and skip this step — the host switched it off on purpose (SO#1448), so this is never a failure, even when the committed mode is `strict`. Otherwise, if the field is missing or `false`, skip this step entirely — skill behavior is unchanged.
|
|
305
328
|
|
|
306
329
|
If `enabled` is `true`:
|
|
307
330
|
|
|
308
331
|
a. Check `$CONFIG."vault-integration".mode`. If `mode` is `off`, skip the mirror invocation (treat as disabled). If `mode` is absent, default to `warn`.
|
|
309
332
|
|
|
310
|
-
b. Resolve the vault directory: use `$CONFIG."vault-integration"."vault-dir"` if non-null, otherwise fall back to the `$VAULT_DIR` environment variable. If neither is set, emit a warning and skip.
|
|
333
|
+
b. Resolve the vault directory: use `$CONFIG."vault-integration"."vault-dir"` if non-null, otherwise fall back to the `$VAULT_DIR` environment variable. If neither is set, emit a warning and skip. Report `vault-dir=<path> (Quelle: <source>)`, where `<source>` is `$CONFIG."vault-integration"."vault-dir-source"` (`env`, `match`, `owner`, `committed`), or `env VAULT_DIR` when the fallback was used.
|
|
311
334
|
|
|
312
|
-
c. Invoke the mirror script. Derive a synthetic `EVOLVE_SESSION_ID` so the vault-mirror auto-commit phase (#31) produces a traceable commit subject (`chore(vault): mirror evolve-<date> — N learnings + 0 sessions`). Pass `--vault-name` when `vault-integration.vault-name` is set in Session Config:
|
|
335
|
+
c. Invoke the mirror script from the repo root, by its plugin path — `--repo-root "$PWD"` plus an absolute `--source` make the vault namespace this repo's identity, and a ledger from a different repo is refused (exit 2, `source-repo-mismatch`, #1503) instead of overwriting another project's notes. Derive a synthetic `EVOLVE_SESSION_ID` so the vault-mirror auto-commit phase (#31) produces a traceable commit subject (`chore(vault): mirror evolve-<date> — N learnings + 0 sessions`). Pass `--vault-name` when `vault-integration.vault-name` is set in Session Config:
|
|
313
336
|
```bash
|
|
314
337
|
EVOLVE_SESSION_ID="evolve-$(date -u +%Y-%m-%d-%H%M)"
|
|
315
338
|
EVOLVE_VAULT_NAME=$(echo "$CONFIG" | jq -r '."vault-integration"."vault-name" // empty')
|
|
316
339
|
node "$PLUGIN_ROOT/scripts/vault-mirror.mjs" \
|
|
317
340
|
--vault-dir "<vault-dir>" \
|
|
318
|
-
--
|
|
341
|
+
--repo-root "$PWD" \
|
|
342
|
+
--source "$PWD/.orchestrator/metrics/learnings.jsonl" \
|
|
319
343
|
--kind learning \
|
|
320
344
|
--session-id "$EVOLVE_SESSION_ID" \
|
|
321
345
|
${EVOLVE_VAULT_NAME:+--vault-name "$EVOLVE_VAULT_NAME"}
|
|
@@ -25,7 +25,7 @@ description: >
|
|
|
25
25
|
|
|
26
26
|
The user invoked `/memory-cleanup` with arguments: **$ARGUMENTS**. Parse them before anything else — two optional, mutually-exclusive flags are recognised (`--dry-run`, `--apply-pending`, see PRD #502); passing both is an error, and the absence of both selects the legacy interactive 4-phase mode. The per-flag behaviour, the exact status lines and the exit codes are in § Argument Handling (Phase 0) below; the interactive default runs Phases 1-4 (Orient → Gather Signal → Consolidate → Prune & Index) against `~/.claude/projects/<encoded-cwd>/memory/` and reports per § Output.
|
|
27
27
|
|
|
28
|
-
Sidecar producer/consumer contract: session-
|
|
28
|
+
Sidecar producer/consumer contract: session-start's `maintenance-due` probe uses `scripts/lib/auto-dream.mjs` to report when cleanup is due; the former session-end Phase 3.6.5 nudge was retired in #1288. Neither path generates the sidecar. The only real producer of `.orchestrator/pending-dream.md` is a manual `/memory-cleanup --dry-run` run; `--apply-pending` is the operator-confirmed consumer in a later session. The sidecar file is single-writer — concurrent sessions cannot collide because the writer holds the session-lock. <!-- path-check: example -->
|
|
29
29
|
|
|
30
30
|
Implements the 4-phase memory consolidation process modelled after Claude Code's Auto Dream feature. Run after major refactors, framework migrations, or every 5+ sessions in a repo.
|
|
31
31
|
|
|
@@ -184,8 +184,8 @@ Dispatch one Agent per persona from the active catalog set.
|
|
|
184
184
|
|
|
185
185
|
**Model selection per persona:**
|
|
186
186
|
- If `persona.model` is a full Claude model ID (`MODEL_ID_RE`): use it as-is.
|
|
187
|
-
- If `persona.model` is `opus` or unset AND `persona.tier == 'domain-expert'`:
|
|
188
|
-
`
|
|
187
|
+
- If `persona.model` is `opus` or unset AND `persona.tier == 'domain-expert'`: use the alias
|
|
188
|
+
`opus`, which tracks the current Opus — pin a full ID only for a named reproducibility need (empirically validated — Opus finds real problems Sonnet misses; see vault
|
|
189
189
|
learning `[[persona-opus-finds-real-failing-cibadge]]`).
|
|
190
190
|
- Otherwise: use the persona's declared model alias.
|
|
191
191
|
|
|
@@ -28,7 +28,7 @@ name: ai-expert
|
|
|
28
28
|
schema_version: 1
|
|
29
29
|
version: "2"
|
|
30
30
|
role: "AI/ML domain expert — evaluates technical accuracy and implementation quality"
|
|
31
|
-
model:
|
|
31
|
+
model: opus
|
|
32
32
|
tier: domain-expert
|
|
33
33
|
output_contract:
|
|
34
34
|
type: object
|
|
@@ -59,7 +59,7 @@ evaluation_criteria:
|
|
|
59
59
|
| `schema_version` | integer | Must be `1`. |
|
|
60
60
|
| `version` | string | Non-empty string. Persona content version. Increment on any output-affecting change. Used in sidecar + trend-tracking (#459). |
|
|
61
61
|
| `role` | string | Identity statement. Injected verbatim as prompt opener. Keep under 200 chars. |
|
|
62
|
-
| `model` | string | Full model ID (`MODEL_ID_RE`) or alias (`inherit|sonnet|opus|haiku`). Validated at load time. Recommend `
|
|
62
|
+
| `model` | string | Full model ID (`MODEL_ID_RE`) or alias (`inherit|sonnet|opus|haiku`). Validated at load time. Recommend the Opus tier via alias `opus` for `domain-expert` and `compliance` tiers (Opus finds real issues Sonnet misses — vault learning `[[persona-opus-finds-real-failing-cibadge]]`); the alias tracks the current Opus; pin a full model ID only for a named reproducibility need, and name the reason. |
|
|
63
63
|
| `output_contract` | object | Inline JSON Schema Draft 2020-12. `$ref/$defs/allOf/anyOf` FORBIDDEN (H3). Must require `verdict` + `rationale`. AJV compile wrapped in 2s AbortSignal timeout. |
|
|
64
64
|
| `evaluation_criteria` | array | Non-empty. Each string max 512 chars. Injected wrapped in `<persona-criteria>` delimiters (M1). Write as statements, not questions. |
|
|
65
65
|
| `tier` | enum | `domain-expert` \| `buyer-persona` \| `auditor` \| `compliance` \| `reviewer` \| `custom`. Affects model selection (Phase 3). |
|
|
@@ -3,7 +3,7 @@ name: designer-lens
|
|
|
3
3
|
schema_version: 1
|
|
4
4
|
version: "1"
|
|
5
5
|
role: "Designer lens — evaluates interaction flow, cognitive load, consistency, and accessibility"
|
|
6
|
-
model:
|
|
6
|
+
model: opus
|
|
7
7
|
tier: domain-expert
|
|
8
8
|
evaluation_criteria:
|
|
9
9
|
- "The interaction flow is legible on first encounter — a new user can predict what happens next without hidden state."
|
|
@@ -3,7 +3,7 @@ name: engineer-lens
|
|
|
3
3
|
schema_version: 1
|
|
4
4
|
version: "1"
|
|
5
5
|
role: "Engineer lens — evaluates feasibility, complexity, failure modes, and long-term maintainability"
|
|
6
|
-
model:
|
|
6
|
+
model: opus
|
|
7
7
|
tier: domain-expert
|
|
8
8
|
evaluation_criteria:
|
|
9
9
|
- "Failure modes are enumerated — what breaks under load, bad input, or partial failure, and how the system responds."
|
|
@@ -3,7 +3,7 @@ name: pm-lens
|
|
|
3
3
|
schema_version: 1
|
|
4
4
|
version: "1"
|
|
5
5
|
role: "Product Manager lens — evaluates user value, outcome-metric clarity, and scope discipline"
|
|
6
|
-
model:
|
|
6
|
+
model: opus
|
|
7
7
|
tier: domain-expert
|
|
8
8
|
evaluation_criteria:
|
|
9
9
|
- "The change is tied to a specific, named user or business problem — not a solution in search of one."
|