session-orchestrator 3.17.0 → 3.19.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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/030-wave-execution.mdc +17 -1
- package/CHANGELOG.md +105 -412
- package/README.md +12 -9
- package/SECURITY.md +190 -27
- package/agents/AGENTS.md +20 -3
- package/agents/code-implementer.md +6 -6
- package/agents/db-specialist.md +1 -1
- package/agents/qa-strategist.md +31 -6
- package/agents/schemas/qa-strategist.schema.json +27 -0
- package/agents/schemas/test-writer.schema.json +60 -2
- package/agents/security-reviewer.md +1 -1
- package/agents/session-reviewer.md +1 -1
- package/agents/test-writer.md +29 -10
- package/agents/ui-developer.md +1 -1
- package/commands/contract-version-bump.md +28 -0
- package/commands/portfolio.md +1 -1
- package/docs/USER-GUIDE.md +8 -3
- package/docs/ci-setup.md +121 -7
- package/docs/codex-setup.md +1 -1
- package/docs/components.md +6 -6
- package/docs/cursor-setup.md +22 -9
- package/docs/events-schema.md +5 -1
- package/docs/instruction-delivery.md +444 -0
- package/docs/rule-authoring.md +58 -9
- package/docs/session-config-reference.md +244 -9
- package/docs/session-config-template.md +39 -3
- package/hooks/_lib/guard-source-loader.mjs +467 -0
- package/hooks/_lib/lock-bootstrap.mjs +21 -0
- package/hooks/_lib/vcs-create-matcher.mjs +119 -0
- package/hooks/config-protection.mjs +0 -0
- package/hooks/enforce-commands.mjs +10 -2
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +11 -2
- package/hooks/hooks-pi.json +10 -0
- package/hooks/hooks.json +21 -1
- package/hooks/on-session-end.mjs +178 -18
- package/hooks/on-session-start.mjs +23 -0
- package/hooks/post-bash-write-verify.mjs +977 -0
- package/hooks/post-subagent-discovery-validator.mjs +256 -41
- package/hooks/pre-bash-destructive-guard.mjs +525 -160
- package/hooks/pre-bash-issue-budget.mjs +167 -0
- package/hooks/pre-bash-sessions-ledger-guard.mjs +627 -0
- package/hooks/pre-bash-templates-first.mjs +96 -63
- package/hooks/subagent-telemetry.mjs +527 -37
- package/package.json +5 -2
- package/pi/prompts/contract-version-bump.md +12 -0
- package/rules/README.md +32 -0
- package/scripts/archive-closed-prds.mjs +12 -22
- package/scripts/autopilot-multi.mjs +103 -20
- package/scripts/backfill-abandoned-sessions.mjs +160 -4
- package/scripts/check-doc-consistency.sh +17 -1
- package/scripts/eval-session.mjs +50 -9
- package/scripts/fleet-instruction-scan.mjs +141 -0
- package/scripts/lib/autopilot/mr-draft.mjs +31 -1
- package/scripts/lib/autopilot/worktree-pipeline.mjs +113 -5
- package/scripts/lib/backlog-scan.mjs +39 -6
- package/scripts/lib/blocked-commands-policy.mjs +340 -0
- package/scripts/lib/ci-status-banner.mjs +75 -12
- package/scripts/lib/claude-md-budget-lint.mjs +283 -34
- package/scripts/lib/command-blocker.mjs +1013 -58
- package/scripts/lib/config/config-protection.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +9 -1
- package/scripts/lib/config/gitlab-portfolio.mjs +1 -1
- package/scripts/lib/config/issue-budget.mjs +123 -0
- package/scripts/lib/config/reconcile.mjs +21 -0
- package/scripts/lib/config/section-extractor.mjs +121 -1
- package/scripts/lib/config-schema.mjs +23 -3
- package/scripts/lib/config.mjs +17 -0
- package/scripts/lib/convergence-monitor.mjs +49 -3
- package/scripts/lib/description-surface.mjs +535 -0
- package/scripts/lib/dispatcher/enumerate.mjs +26 -40
- package/scripts/lib/ecosystem-wizard/config-writer.mjs +26 -24
- package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +1 -1
- package/scripts/lib/eval/engine.mjs +47 -5
- package/scripts/lib/events.mjs +59 -7
- package/scripts/lib/gates/gate-full.mjs +15 -3
- package/scripts/lib/gates/gate-helpers.mjs +132 -6
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +28 -8
- package/scripts/lib/gitlab-portfolio/aggregator.mjs +8 -2
- package/scripts/lib/gitlab-portfolio/cli.mjs +1 -1
- package/scripts/lib/handover-gate.mjs +7 -3
- package/scripts/lib/harness-audit/categories/category4.mjs +9 -3
- package/scripts/lib/instruction-budget-guard.mjs +402 -51
- package/scripts/lib/io.mjs +345 -10
- package/scripts/lib/issue-budget.mjs +269 -0
- package/scripts/lib/issue-close-strip-labels.mjs +39 -9
- package/scripts/lib/label-scope.mjs +47 -0
- package/scripts/lib/learnings/schema.mjs +43 -3
- package/scripts/lib/lock-reaper.mjs +1 -2
- package/scripts/lib/memory-proposals/schema.mjs +36 -1
- package/scripts/lib/peer-discovery.mjs +645 -0
- package/scripts/lib/pi-hook-bridge.mjs +146 -17
- package/scripts/lib/product-repo-detect.mjs +9 -8
- package/scripts/lib/project-hygiene.mjs +432 -0
- package/scripts/lib/quality-gate.mjs +167 -0
- package/scripts/lib/recommendations-v0.mjs +1 -1
- package/scripts/lib/reconcile/eligibility.mjs +1 -1
- package/scripts/lib/reconcile/emitter.mjs +23 -4
- package/scripts/lib/reconcile/engine.mjs +147 -39
- package/scripts/lib/reconcile/idempotency.mjs +114 -14
- package/scripts/lib/reconcile-nudge-banner.mjs +65 -9
- package/scripts/lib/resource-probe/evaluate.mjs +70 -4
- package/scripts/lib/resource-probe.mjs +19 -0
- package/scripts/lib/rule-loader.mjs +6 -0
- package/scripts/lib/scope-baseline.mjs +564 -0
- package/scripts/lib/scope-gate.mjs +399 -98
- package/scripts/lib/session-close-backfill.mjs +61 -6
- package/scripts/lib/session-end/phase-skip.mjs +1 -0
- package/scripts/lib/session-id.mjs +221 -41
- package/scripts/lib/session-lock.mjs +304 -6
- package/scripts/lib/session-schema/constants.mjs +22 -3
- package/scripts/lib/session-schema/validator.mjs +16 -0
- package/scripts/lib/sessions-integrity-banner.mjs +294 -0
- package/scripts/lib/sessions-staleness-banner.mjs +121 -12
- package/scripts/lib/skill-evolution/idempotency.mjs +135 -16
- package/scripts/lib/skill-evolution/mr-opener.mjs +9 -1
- package/scripts/lib/spiral-carryover.mjs +142 -30
- package/scripts/lib/state-md/mission-status.mjs +53 -3
- package/scripts/lib/subagents-schema.mjs +43 -9
- package/scripts/lib/test-runner/issue-reconcile.mjs +53 -13
- package/scripts/lib/tests-src-ratio.mjs +484 -0
- package/scripts/lib/validate/check-agents.mjs +56 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +244 -10
- package/scripts/lib/validate/check-rules.mjs +217 -35
- package/scripts/lib/validate/check-test-value-bans.mjs +782 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate-vendored-rules.mjs +10 -2
- package/scripts/lib/vault-archive.mjs +17 -2
- package/scripts/lib/vault-backfill/glab.mjs +8 -0
- package/scripts/lib/vault-mirror/process.mjs +30 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +293 -36
- package/scripts/lib/vcs-repo-spec.mjs +362 -0
- package/scripts/lib/wave-resource-gate.mjs +115 -11
- package/scripts/lib/worktree/listing.mjs +44 -7
- package/scripts/mcp-server.sh +17 -3
- package/scripts/measure-context-overhead.sh +151 -0
- package/scripts/memory-propose.mjs +72 -9
- package/scripts/print-applicable-rules.mjs +51 -12
- package/scripts/release.mjs +534 -0
- package/scripts/run-quality-gate.mjs +123 -5
- package/scripts/validate-wave-scope.mjs +182 -17
- package/scripts/vault-integration-watcher.mjs +32 -10
- package/skills/_shared/config-reading.md +2 -2
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +145 -28
- package/skills/contract-version-bump/SKILL.md +219 -0
- package/skills/discovery/SKILL.md +4 -4
- package/skills/discovery/issue-templates.md +11 -11
- package/skills/discovery/probes-audit.md +1 -1
- package/skills/discovery/probes-feature.md +1 -1
- package/skills/discovery/probes-session.md +26 -5
- package/skills/ecosystem-health/SKILL.md +1 -1
- package/skills/ecosystem-health/wizard.md +4 -4
- package/skills/evolve/SKILL.md +1 -0
- package/skills/gitlab-ops/SKILL.md +20 -12
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/hook-development/SKILL.md +1 -1
- package/skills/mode-selector/SKILL.md +1 -1
- package/skills/npm-publish/SKILL.md +17 -1
- package/skills/plan/SKILL.md +5 -5
- package/skills/plan/mode-feature.md +4 -4
- package/skills/plan/mode-new.md +10 -10
- package/skills/plan/mode-retro.md +1 -1
- package/skills/quality-gates/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +21 -4
- package/skills/session-end/SKILL.md +34 -13
- package/skills/session-end/discovery-scan.md +4 -2
- package/skills/session-end/drift-operations.md +4 -4
- package/skills/session-end/metrics-collection.md +13 -0
- package/skills/session-end/phase-3-2-docs-verification.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +2 -1
- package/skills/session-end/plan-verification.md +5 -2
- package/skills/session-end/vault-operations.md +1 -1
- package/skills/session-end/verification-checklist.md +1 -1
- package/skills/session-plan/SKILL.md +6 -2
- package/skills/session-plan/wave-template.md +2 -0
- package/skills/session-start/SKILL.md +73 -7
- package/skills/session-start/phase-4-5-resource-health.md +15 -2
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/vault-sync/validator.mjs +108 -7
- package/skills/wave-executor/SKILL.md +5 -2
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/wave-loop.md +163 -10
- package/templates/_shared/loop.md +4 -4
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
Resolve the effective `discovery-on-close` value:
|
|
9
9
|
|
|
10
10
|
```
|
|
11
|
-
effectiveDiscoveryOnClose = config.discoveryOnClose ??
|
|
11
|
+
effectiveDiscoveryOnClose = config.discoveryOnClose ?? true
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
- If the user has set `discovery-on-close` explicitly in Session Config, that value always wins (backward compatible).
|
|
15
|
-
- If the field is absent (not configured), the default is
|
|
15
|
+
- If the field is absent (not configured), the default is `true` for **every** session type.
|
|
16
|
+
|
|
17
|
+
**Why this is no longer session-type aware (2026-07-29).** The previous default was `false` for `housekeeping` and `true` for `feature`/`deep`, on the theory that housekeeping is lightweight and does not need a scan. Measurement inverted that theory: a read-only diagnostic run of the housekeeping flow across six real consumer repos found the discovery probes are the only substantial project-hygiene surface in the whole system — session-start Phase 4 runs 13 probes, of which exactly two (`ci-status`, `project-hygiene`) inspect the project rather than this tool's own substrate. Defaulting the scan OFF for housekeeping meant the one session type whose entire purpose is cleanup was also the only one running without hygiene diagnostics. Repos that genuinely want the faster close still set `discovery-on-close: false` explicitly, which continues to win.
|
|
16
18
|
|
|
17
19
|
If `effectiveDiscoveryOnClose` is `false`, skip this section.
|
|
18
20
|
|
|
@@ -55,8 +55,8 @@ fi
|
|
|
55
55
|
**Reporting rules:**
|
|
56
56
|
|
|
57
57
|
- **`mode: off`** — checker reports `status: skipped-mode-off`; include a single line "CLAUDE.md drift: skipped (mode=off)" in the quality gate report. Never blocks.
|
|
58
|
-
- **`mode: warn`** — checker always exits 0. If `.errors | length > 0`, surface the list in the report under "CLAUDE.md drift warnings (mode=warn)" with check + file:line + message for each entry. Also list any `.warnings` (e.g. `#NN` the checker could not resolve via glab). Never blocks close; note that `mode:
|
|
59
|
-
- **`mode:
|
|
58
|
+
- **`mode: warn`** — checker always exits 0. If `.errors | length > 0`, surface the list in the report under "CLAUDE.md drift warnings (mode=warn)" with check + file:line + message for each entry. Also list any `.warnings` (e.g. `#NN` the checker could not resolve via glab). Never blocks close; note that `mode: strict` would have routed the same errors through the carryover path below.
|
|
59
|
+
- **`mode: strict`** (legacy alias `hard`, normalized to `strict` at parse time — #217) — checker exits 1 on errors. On exit 1: do NOT block the close. Surface the full error list, then default to **warn + carryover + continue** (Recommended): file a carryover issue (labels `carryover`, `priority::high`) titled `[Carryover] CLAUDE.md drift (strict) — <E> errors` capturing the drift items for a follow-up session, log a Deviation entry in STATE.md `## Deviations`, then continue the close. Offer "Override and close" (continue without a carryover issue; log the Deviation) as an alternative via AskUserQuestion. The user can also (a) fix the drift directly in `CLAUDE.md` (or `AGENTS.md` on Codex CLI) / `_meta/`, or (b) temporarily set `mode: warn` while backfilling, or (c) disable a specific check via its `check-*` flag if it reports false positives on this codebase.
|
|
60
60
|
- **Exit 2** (infra error — missing `node`, unreadable `VAULT_DIR`, malformed args) — treat as a skipped gate with a loud warning ("CLAUDE.md drift: infrastructure error — <reason>"). Do NOT block the session close on infra failures.
|
|
61
61
|
|
|
62
62
|
**Exit-code dispatch:** The checker writes infra-error JSON to stderr (suppressed by `2>/dev/null` above), so `DC_JSON` is empty when `DC_EXIT == 2`. Always branch on `DC_EXIT` first, then `DC_STATUS`:
|
|
@@ -64,8 +64,8 @@ fi
|
|
|
64
64
|
```bash
|
|
65
65
|
if [[ "$DC_EXIT" == "2" ]]; then
|
|
66
66
|
# infra error — DC_JSON is empty, stderr was suppressed. Surface loud warning, do not block.
|
|
67
|
-
elif [[ "$DC_STATUS" == "invalid" && "$DC_MODE" == "hard" ]]; then
|
|
68
|
-
#
|
|
67
|
+
elif [[ "$DC_STATUS" == "invalid" && ( "$DC_MODE" == "strict" || "$DC_MODE" == "hard" ) ]]; then
|
|
68
|
+
# strict-mode (legacy alias `hard`): surface + warn + carryover + continue (no hard block — #724)
|
|
69
69
|
elif [[ "$DC_STATUS" == "invalid" ]]; then
|
|
70
70
|
# warn-mode report
|
|
71
71
|
else
|
|
@@ -169,6 +169,19 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
169
169
|
> - `effectiveness`: ALWAYS populated from Phase 1 plan verification results, and CONSTRUCTED EXPLICITLY in the METRICS_ENTRY snippet (#773) — never deferred to a "remember to add" optional step (that omission is how `carryover: 0` slipped past 41 records). `completion_rate` = `completed / planned_issues` (0.0-1.0, where 0.0 means nothing was completed). **`carryover` counting rule (#773):** `carryover` is the **length of the Phase 1.65 gate carry-list** — `autoCarry` ∪ the middle-band `ask` items the operator LEFT SELECTED ∪ the answered-question `impliesWork: true` candidates — NOT the raw Phase 1.2+1.3 candidate count. On the fail-open skip (gate disabled / headless / AUQ unavailable), EVERY candidate carries, so `carryover` = the full candidate-list length. Count the gate's OUTPUT (what reaches Phase 5 Step 3 filing), not its INPUT.
|
|
170
170
|
> - `effectiveness.override_ratio` (#730/H5): OPTIONAL nested field = `overridden_findings / max(total_findings_surfaced, 1)` (float 0.0-1.0). Populate ONLY when Phase 2.6 (Broken-Window Budget) ran this session (`broken-window-budget.enabled: true`). OMIT (do NOT write null/0) otherwise — **absent = "not measured"**, `0.0` = "measured, nothing overridden". `overridden_findings` = the summed `count` of the `orchestrator.finding.overridden` events emitted this session; `total_findings_surfaced` = every MED/LOW+ finding surfaced across Phase 1.8 + wave reviewers.
|
|
171
171
|
> - `waves[].planned_files_count` / `waves[].over_delivery_ratio` (#730/H4): OPTIONAL per-wave fields, populated from STATE.md Wave History headers of the form `(planned <P> files → actual <A>, over-delivery <R>)` (written by wave-executor §3a since #730/H4); omit when absent (pre-#730 sessions / grounding-check: false).
|
|
172
|
+
> - `waves[].suite_passed` / `waves[].suite_failed` / `waves[].suite_platform` (#944): OPTIONAL per-wave fields. Omit all three when absent — absent = "not measured", `suite_failed: 0` = "measured, zero failures".
|
|
173
|
+
> **`suite_passed` / `suite_failed`: read the event FIRST, the STATE.md header only as fallback (#966 step 3).** Since #954/#967 the between-waves gate wrapper `scripts/run-quality-gate.mjs` emits `orchestrator.quality_gate.{passed,failed}` with a machine-measured `counts: {passed, failed, total}` AND the `wave_number` it resolved from the `wave-scope.json` sidecar, so per-wave attribution needs no wall-clock window join. Payload fields are flat at the record's top level; for each wave `N` of this session:
|
|
174
|
+
>
|
|
175
|
+
> ```bash
|
|
176
|
+
> jq -c --argjson w N --arg s "<semantic_session_id>" '
|
|
177
|
+
> select(.event | startswith("orchestrator.quality_gate."))
|
|
178
|
+
> | select(.semantic_session_id == $s and .wave_number == $w and .counts != null)
|
|
179
|
+
> | .counts' .orchestrator/metrics/events.jsonl | tail -1
|
|
180
|
+
> ```
|
|
181
|
+
>
|
|
182
|
+
> Filtering by `semantic_session_id` is mandatory — `events.jsonl` accumulates across sessions and every past session also had a wave `N`. Take the LAST matching record (the wave's final gate run); `counts.passed` → `suite_passed`, `counts.failed` → `suite_failed`. No match = the field was not measured for that wave → omit, never zero-fill (the producer already omits `counts` rather than zero-filling when a run fail-fast'd before the test gate).
|
|
183
|
+
> **Fallback, still live:** when no event matches, fall back to the STATE.md Wave History header `— suite <passed>/<failed> on <platform>` (written by wave-executor §3a since #944). Three cases genuinely need it: pre-#954 sessions, a gate run outside the `run-quality-gate.mjs` wrapper, and the `verification-auto-fix` producer in `scripts/lib/quality-gate.mjs`, which emits `counts` but no `wave_number` (its records are mid-wave retries, so not matching the selector is correct).
|
|
184
|
+
> **`suite_platform` has no event source at all** and is read from the STATE.md header, unchanged. **Remaining work to retire the prose path fully:** (1) carry the platform on the gate event payload; (2) once a session has landed with the event path green and no fallback hits, drop the hand-written trio from `wave-loop.md` step 7. Until both hold, the trio stays written — deleting the writer before the reader is proven loses the numbers for sessions in flight.
|
|
172
185
|
> - `open_questions_asked` / `open_questions_answered` / `open_questions_deferred` (#773): the three open-question counts from the Phase 1.65 gate's AUQ Call 2 (identical to the `questions_*` payload fields on the `orchestrator.handover.gated` event). Top-level, additive, non-negative integers. Populate ONLY when the gate ran an interactive triage ("Closen + Triage" path). OMIT all three (do NOT write `0`) when the gate was skipped (fail-open / headless / disabled) or took the fast-path — absent = "not measured", `0` = "measured, zero questions". Validator accepts absent/null/non-negative-integer.
|
|
173
186
|
> - `stagnation_events`: populated ONLY when ≥1 stagnation event was logged to `events.jsonl` during this session. When `total == 0`, the field is omitted from the JSONL entry.
|
|
174
187
|
> - `grounding_injections`: populated ONLY when ≥1 `orchestrator.grounding.injected` event was logged to `events.jsonl` during this session. When `count == 0`, the field is omitted from the JSONL entry.
|
|
@@ -110,7 +110,7 @@ Read `docs-orchestrator.mode` from Session Config (default: `warn`).
|
|
|
110
110
|
|
|
111
111
|
Gap tasks: <list task IDs and target-patterns>
|
|
112
112
|
```
|
|
113
|
-
- On "Warn + carryover and close": file a carryover issue (labels `carryover`, `priority
|
|
113
|
+
- On "Warn + carryover and close": file a carryover issue (labels `carryover`, `priority::high`) titled `[Carryover] Documentation gaps (strict) — <gap-count> tasks` listing the gap task IDs + target-patterns for a follow-up session, log the deviation (below), then append the report and continue the close.
|
|
114
114
|
- On "Override": log a deviation in the `## Deviations` section of STATE.md:
|
|
115
115
|
```
|
|
116
116
|
- [Phase 3.2] docs-orchestrator strict-mode gaps overridden by user. Tasks: <ids>. Timestamp: <ISO 8601>.
|
|
@@ -241,7 +241,7 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
241
241
|
|
|
242
242
|
#### Coordinator-direct procedure
|
|
243
243
|
|
|
244
|
-
1. Read Session Config: `reconcile.enabled` (default `false`), `reconcile['rule-expiry-days']` (default `null` — falls back to per-type TTL in the engine), `reconcile['confidence-floor']` (default `0.5`), `reconcile['min-rule-days']` (default `7` — floor window (days) applied to a proposed rule's `expires-at` so a near-dead or already-elapsed natural expiry never produces a born-dead rule, issue #741.1), `reconcile['min-insight-chars']` (default `24` — opt-in minimum insight length gating the eligibility placeholder-insight check, issue #741.2). If `reconcile.enabled` is not `true`, log `reconcile: disabled (reconcile.enabled=false)` and skip all remaining steps.
|
|
244
|
+
1. Read Session Config: `reconcile.enabled` (default `false`), `reconcile['rule-expiry-days']` (default `null` — falls back to per-type TTL in the engine), `reconcile['confidence-floor']` (default `0.5`), `reconcile['min-rule-days']` (default `7` — floor window (days) applied to a proposed rule's `expires-at` so a near-dead or already-elapsed natural expiry never produces a born-dead rule, issue #741.1), `reconcile['min-insight-chars']` (default `24` — opt-in minimum insight length gating the eligibility placeholder-insight check, issue #741.2), `reconcile['max-proposals-per-run']` (default `10` — volume brake, issue #900 D; the engine sorts eligible learnings by confidence DESC and proposes at most this many per run). If `reconcile.enabled` is not `true`, log `reconcile: disabled (reconcile.enabled=false)` and skip all remaining steps.
|
|
245
245
|
|
|
246
246
|
2. Invoke `runReconcile` from `scripts/lib/reconcile/engine.mjs`:
|
|
247
247
|
|
|
@@ -252,6 +252,7 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
252
252
|
ruleExpiryDays: config.reconcile['rule-expiry-days'] ?? undefined,
|
|
253
253
|
minRuleDays: config.reconcile['min-rule-days'] ?? undefined,
|
|
254
254
|
minInsightChars: config.reconcile['min-insight-chars'] ?? undefined,
|
|
255
|
+
maxProposalsPerRun: config.reconcile['max-proposals-per-run'] ?? undefined,
|
|
255
256
|
now: new Date(),
|
|
256
257
|
});
|
|
257
258
|
```
|
|
@@ -43,13 +43,16 @@ Compare the files the plan said would be touched against the files actually chan
|
|
|
43
43
|
- Test files (`*.test.*`, `*.spec.*`, `**/__tests__/**`) corresponding to a touched production file are reclassified as expected (not scope creep)
|
|
44
44
|
- Generated/lock files (`pnpm-lock.yaml`, `*.lock`, `dist/**`, `node_modules/**`) are excluded from both planned and actual sets
|
|
45
45
|
- The `.claude/`, `.codex/`, and `.cursor/` state directories are excluded — they are session artifacts, not code
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
> **Scope-drift cross-reference:** the S2 warn-only drift tripwire (below) uses its own separately-maintained filter list — `DRIFT_EXCLUDE_PATTERNS` in `scripts/lib/scope-baseline.mjs` — and is NOT derived from the filters above. That list is the shared filter source for both sides of its ratio IN CODE: `writeBaseline()`'s denominator (`countPlannedFiles()`) and `computeDrift()`'s numerator both call the same internal `filterExcluded()` helper (#894 review finding F1 — previously only the numerator was code-filtered; the denominator relied on a coordinator prose instruction to pre-filter before calling `writeBaseline()`, which is why three earlier PRD revisions shipped a tripwire that read a wrong ratio).
|
|
48
|
+
5. **Report** in the verification output. Also call `computeDrift({ repoRoot, threshold: 2.0 })` (`scripts/lib/scope-baseline.mjs`) and append its result — warn-only, informational, never blocks close:
|
|
47
49
|
```
|
|
48
50
|
File-level grounding:
|
|
49
51
|
- Planned: N files
|
|
50
52
|
- Touched: N files (X% coverage)
|
|
51
53
|
- Unplanned (scope creep): N files [list first 5]
|
|
52
54
|
- Untouched (planned but not edited): N files [list first 5]
|
|
55
|
+
- Scope drift: filesRatio X.X (Y actual / Z planned, threshold 2.0) — [breached | ok | skipped: <reason>]
|
|
53
56
|
```
|
|
54
57
|
6. **Append to session metrics** (`grounding` field in the Phase 1.7 JSONL entry):
|
|
55
58
|
```json
|
|
@@ -66,7 +69,7 @@ Compare the files the plan said would be touched against the files actually chan
|
|
|
66
69
|
- Document what was completed and what remains
|
|
67
70
|
- Create a VCS issue for the remaining work with:
|
|
68
71
|
- Title: `[Carryover] <original task description>`
|
|
69
|
-
- Labels: `priority
|
|
72
|
+
- Labels: `priority::<original>`, `status:ready`
|
|
70
73
|
- Description: what's done, what's left, context for next session
|
|
71
74
|
- Link to original issue if applicable
|
|
72
75
|
|
|
@@ -41,7 +41,7 @@ fi
|
|
|
41
41
|
|
|
42
42
|
- **`mode: off`** — validator reports `status: skipped-mode-off`; include a single line "Vault validation: skipped (mode=off)" in the quality gate report and move on. Never blocks.
|
|
43
43
|
- **`mode: warn`** — validator always exits 0. If `.errors | length > 0`, surface the error list in the report under "Vault validation warnings (mode=warn)" with file + path + message for each entry. Also list any `.warnings` (dangling wiki-links) in the same section. Never blocks close, but remind the user that flipping to `mode: hard` would have blocked on N files.
|
|
44
|
-
- **`mode: hard`** — validator exits 1 on errors. On exit 1: do NOT block the close. Surface the full error list in the quality gate report, then default to **warn + carryover + continue** (Recommended): file a carryover issue (labels `carryover`, `priority
|
|
44
|
+
- **`mode: hard`** — validator exits 1 on errors. On exit 1: do NOT block the close. Surface the full error list in the quality gate report, then default to **warn + carryover + continue** (Recommended): file a carryover issue (labels `carryover`, `priority::high`) titled `[Carryover] Vault validation (hard) — <N> frontmatter errors` capturing the offending files for a follow-up session, log a Deviation entry in STATE.md `## Deviations`, then continue the close. Offer "Override and close" (continue without a carryover issue; log the Deviation) as an alternative via AskUserQuestion. The user can also (a) fix the offending frontmatter, (b) add the file pattern to `vault-sync.exclude` if it is a legitimate index file (e.g. `_MOC.md`, `_overview.md`), or (c) temporarily set `vault-sync.mode: warn` while backfilling frontmatter across the vault. On exit 0 with warnings: include them in the report but do not block.
|
|
45
45
|
- **Exit 2** (infra error — missing `node`, `pnpm`, or `validator.mjs`) — treat as a skipped gate with a loud warning ("Vault validation: infrastructure error — <reason>"). Do NOT block the session close on infra failures; the goal is to surface configuration problems, not to wedge sessions when Node is unavailable.
|
|
46
46
|
|
|
47
47
|
**Success line format** (when `errors: [] && warnings: []`):
|
|
@@ -180,7 +180,7 @@ For each task from Step 1, assign exactly one role. Use these signal-to-role map
|
|
|
180
180
|
**Disambiguation rules:**
|
|
181
181
|
- If a task involves BOTH exploration AND implementation → split it: Discovery agent reads/validates, Impl-Core agent implements. Create two separate task entries.
|
|
182
182
|
- If a task is "fix something from a previous session" (not from this session's Impl-Core) → classify as **Impl-Core** (it is new work for this session).
|
|
183
|
-
-
|
|
183
|
+
- A "write tests for new feature code being built this session" task is created ONLY when Discovery or a qa-strategist run reported a **named gap** — a concrete bug or regression the current suite would let through, stated as such. When that gap exists, classify the task as **Quality** (not Impl-Core); tests run after implementation. "Feature X was built" is NOT by itself evidence of test demand: with no named gap, no Quality task is created — do not synthesize one to give the role something to do. A dispatched `test-writer` may correspondingly report `no-tests-needed` as a SUCCESS status, not a failure.
|
|
184
184
|
- If unsure between Impl-Core and Impl-Polish → if the task is on the critical path (other tasks depend on it), it is **Impl-Core**. If independent polish, it is **Impl-Polish**.
|
|
185
185
|
- **Docs role** is only active when `docs-orchestrator.enabled: true` in Session Config. When disabled (default), documentation-update tasks fall into **Impl-Polish** (inline doc changes alongside code) or **Finalization** (standalone doc/SSOT updates) as today.
|
|
186
186
|
|
|
@@ -359,7 +359,7 @@ When `docs-orchestrator.enabled: true`, apply the following concrete dispatch ru
|
|
|
359
359
|
- Output: Validated understanding, updated task scope if discoveries warrant it
|
|
360
360
|
- Tools: Read, Grep, Glob, Bash (read-only commands only) — do NOT use Edit or Write
|
|
361
361
|
- Scope enforcement: set `allowedPaths` to `[]` (empty) for Discovery waves. Include in agent prompts: "You are READ-ONLY. Do NOT use Edit or Write tools."
|
|
362
|
-
- Distributional claims MUST follow `.claude/rules/parallel-sessions.md` § PSA-006 — quote the executed
|
|
362
|
+
- Distributional claims AND bare repo-state numbers MUST follow `.claude/rules/parallel-sessions.md` § PSA-006 — quote the executed command + file scope + count + WHEN it was measured. Coordinators REJECT Discovery outputs that assert "N of M" / "100% of X" (deep-1647 W1-D3 incident class) or a bare count like "14 commits" / "92 learnings" (#908) without that evidence. Discovery facts age: re-verify a count before re-briefing it into a later wave.
|
|
363
363
|
|
|
364
364
|
**Impl-Core**
|
|
365
365
|
- Full implementation agents with Write/Edit/Bash access
|
|
@@ -422,6 +422,10 @@ Score the session scope to determine optimal agent counts per wave. Skip for hou
|
|
|
422
422
|
|
|
423
423
|
> Housekeeping sessions skip Discovery (tasks are predefined) and use fixed agent counts regardless of complexity.
|
|
424
424
|
|
|
425
|
+
> **The Quality column is a CAP, not a target.** Every other column sizes to briefed work; the Quality column historically sized to the tier alone, so capacity went looking for work (tests written because a slot existed, not because a gap was measured). Quality capacity must be EARNED by measured demand. Compute the effective count as `min(<tier cap>, ceil((HIGH + MED gaps from the most recent qa-strategist run) / 3))`.
|
|
426
|
+
> - **0 HIGH and 0 MED gaps → the Quality role has 0 test-writing tasks**, and its wave is skipped by the Step 2 empty-role rule. This does NOT touch the read-only review panel (security-reviewer / qa-strategist / architect-reviewer) — that panel reviews, it does not write tests, and it keeps running as configured.
|
|
427
|
+
> - **No qa-strategist signal at all** (no prior measurement this session): allocate a conservative 1-2 test-writers. Never spend the full tier cap blind — an unmeasured tier cap is a guess, and the guess has historically been too high.
|
|
428
|
+
|
|
425
429
|
The `agents-per-wave` Session Config value caps the maximum regardless of tier.
|
|
426
430
|
|
|
427
431
|
If project intelligence (learnings) suggests different sizing based on historical data, prefer the historical recommendation over the formula.
|
|
@@ -32,6 +32,8 @@ For each wave, define agents with:
|
|
|
32
32
|
|
|
33
33
|
Read `agents-per-wave` from Session Config to cap the maximum.
|
|
34
34
|
|
|
35
|
+
> **The Quality column is a CAP, not a target.** Quality capacity is need-gated: the effective count is `min(<column cap>, ceil((HIGH + MED gaps from the most recent qa-strategist run) / 3))`. 0 gaps → 0 test-writing tasks and the wave is skipped (the read-only review panel is unaffected); no qa-strategist signal at all → a conservative 1-2, never the blind cap. Full rule: `SKILL.md` § Agent Count by Tier footnote.
|
|
36
|
+
|
|
35
37
|
> **Note:** For feature and deep sessions, prefer the complexity-based agent counts from Step 3. This table provides defaults when complexity scoring is skipped (housekeeping) or as a fallback.
|
|
36
38
|
|
|
37
39
|
> \* Housekeeping sessions use single-wave serial execution (see wave-executor). Agent counts are for the single consolidated wave, not per-role.
|
|
@@ -339,6 +339,7 @@ Reset rules — applies ONLY on the `completed` branch. Do NOT perform this rese
|
|
|
339
339
|
```
|
|
340
340
|
|
|
341
341
|
Omit individual bullets for null-valued fields. If all 5 are null (i.e., `parseRecommendations` returned non-null but every field is null after type-coercion), skip the archival block entirely.
|
|
342
|
+
7. **Scope-baseline key deletion (Epic #894 S5, #898):** If ANY of the 5 `scope-baseline-*` frontmatter keys (`scope-baseline-intent`, `scope-baseline-owner-boundary`, `scope-baseline-planned-files`, `scope-baseline-session`, `scope-baseline-frozen-at`) is present, remove them via the same `updateFrontmatterFields(contents, {field: null, ...})` mechanism as rule 6 (null value deletes the key). Rule 5 leaves unknown frontmatter fields intact and no other rule removes these five — without this step they survive into session N+1 and silently corrupt the next session's drift-baseline denominator. This is a hygiene layer only: the primary defense is mechanical — `scripts/lib/scope-baseline.mjs` compares `scope-baseline-session` against the canonical `session` field, so a stale baseline self-invalidates (`readBaseline()` returns `{stale: true, …}`) even if this rule were skipped. Delete exactly these five keys; do not remove any other unknown key.
|
|
342
343
|
|
|
343
344
|
Rationale: `/close` intentionally keeps STATE.md as a record so the next session-start can read it. This reset completes that contract by demoting the record before new session state is written, so a fresh session never appears "already completed". The Recommendation archival (rule 6) preserves the session-to-session handoff in a human-readable form after the Recommendations Banner has rendered — Phase B's Mode-Selector will read the LIVE frontmatter of the current session and does not need the archived copy, so this is purely informational for humans browsing STATE.md history.
|
|
344
345
|
|
|
@@ -489,22 +490,36 @@ This is **best-effort**, exactly like the Phase 4 banners: a board-write failure
|
|
|
489
490
|
Run these checks as ONE parallel Bash block — background the independent git ops with `&` and `wait`:
|
|
490
491
|
|
|
491
492
|
```bash
|
|
493
|
+
# Refresh remote-tracking refs BEFORE reading them. Without this, `origin/main`
|
|
494
|
+
# is a snapshot from the last fetch or clone, and every ahead/behind derivation
|
|
495
|
+
# below silently compares against stale data — a repo can read "in sync" while
|
|
496
|
+
# the real remote is many commits ahead. Best-effort and non-blocking: connect
|
|
497
|
+
# timeouts are bounded (no `timeout(1)` — it is absent on macOS by default) and
|
|
498
|
+
# any failure (offline, no remote, auth prompt) falls through to `|| true`,
|
|
499
|
+
# leaving the previous behaviour of reading whatever refs are on disk.
|
|
500
|
+
GIT_SSH_COMMAND='ssh -o ConnectTimeout=5 -o BatchMode=yes' \
|
|
501
|
+
git -c http.lowSpeedLimit=1000 -c http.lowSpeedTime=5 \
|
|
502
|
+
fetch --quiet --prune 2>/dev/null || true
|
|
503
|
+
|
|
492
504
|
# Independent ops — launch in parallel, collect output via tmpfiles
|
|
493
505
|
git branch -a > /tmp/so-branches.$$ &
|
|
494
506
|
git log --oneline -N > /tmp/so-commits.$$ & # N from Session Config `recent-commits` (default 20)
|
|
495
507
|
git status --short > /tmp/so-status.$$ &
|
|
496
|
-
|
|
508
|
+
# `--left-right --count A...B` emits "<behind>\t<ahead>": commits reachable only
|
|
509
|
+
# from origin/main, then only from HEAD. The older `git log origin/main..HEAD`
|
|
510
|
+
# form could express ahead ONLY, so "behind" was structurally unreportable.
|
|
511
|
+
git rev-list --left-right --count origin/main...HEAD > /tmp/so-divergence.$$ 2>/dev/null &
|
|
497
512
|
wait
|
|
498
513
|
# Then read the 4 tmpfiles in a single step and derive: branch state, recent commits,
|
|
499
514
|
# unpushed/uncommitted, open branches. Clean up tmpfiles once derivations are done:
|
|
500
|
-
rm -f /tmp/so-branches.$$ /tmp/so-commits.$$ /tmp/so-status.$$ /tmp/so-
|
|
515
|
+
rm -f /tmp/so-branches.$$ /tmp/so-commits.$$ /tmp/so-status.$$ /tmp/so-divergence.$$
|
|
501
516
|
```
|
|
502
517
|
|
|
503
518
|
Checks to run (derived from the collected output):
|
|
504
519
|
|
|
505
|
-
1. **Branch state**: current branch (from `branch -a`), ahead/behind origin (from `ahead` tmpfile)
|
|
520
|
+
1. **Branch state**: current branch (from `branch -a`), ahead/behind origin (from the `divergence` tmpfile — field 1 is behind, field 2 is ahead). Report BOTH directions. A non-zero behind count means the local branch is missing remote work: surface it, because agents reading repo instructions from a stale checkout will follow superseded guidance. An empty `divergence` tmpfile means no `origin/main` ref resolved (no remote, or a differently-named default branch) — report that as unknown, never as zero.
|
|
506
521
|
2. **Recent commits**: parse `commits` tmpfile — identify last session's work by commit patterns
|
|
507
|
-
3. **Unpushed/uncommitted**: `status` tmpfile +
|
|
522
|
+
3. **Unpushed/uncommitted**: `status` tmpfile + the ahead field of the `divergence` tmpfile combined
|
|
508
523
|
4. **Open branches**: parse `branch -a` tmpfile, identify which are mergeable to develop/main
|
|
509
524
|
5. **Stale branches**: run AFTER the parallel block — requires iterating over branches (depends on `branch -a` output). Use `git log -1 --format=%ct <branch>` per branch; flag those with no commits in more than `stale-branch-days` (default: 7) days.
|
|
510
525
|
|
|
@@ -635,7 +650,7 @@ Using the detected VCS CLI, query (reading `issue-limit` from Session Config, de
|
|
|
635
650
|
5. **Pipeline/CI status** — is CI green?
|
|
636
651
|
|
|
637
652
|
Group issues by:
|
|
638
|
-
- `priority
|
|
653
|
+
- `priority::critical` / `priority::high` — must-address
|
|
639
654
|
- `status:ready` — ready to work on
|
|
640
655
|
- Session-type relevance (housekeeping tasks vs feature tasks vs deep-work tasks)
|
|
641
656
|
|
|
@@ -667,7 +682,8 @@ Group issues by:
|
|
|
667
682
|
|
|
668
683
|
Additionally, if the current repo has a configured `origin` remote and `glab` (GitLab) or `gh` (GitHub) is available, invoke the CI-status probe (`scripts/lib/ci-status-banner.mjs`) via `checkCiStatus({ repoRoot: process.cwd() })`. The helper returns `null` (silent no-op) when no VCS remote, no CLI tool, parse failure, or CLI timeout (8s default). When `result.status === 'red'`, render a banner alongside the bootstrap-lock and vault-staleness warnings:
|
|
669
684
|
- **Red** (`status === 'red'`): `"🚨 CI RED on HEAD (pipeline #<currentPipelineId>) — last green: #<lastGreen.pipelineId> (commit <SHA-7>, <redCount> pipelines ago). Failing job: <failingJobName>"`
|
|
670
|
-
- **Green
|
|
685
|
+
- **Green with soft failures** (`status === 'green'` AND `result.allowFailureJobs` is present): `"⚠ CI green on HEAD, but <N> allow_failure job(s) FAILED: <names>. A pipeline reports success regardless of these — a job red on every run stays invisible at the pipeline level."` Render this even though the pipeline passed: the whole point is that pipeline status cannot express it.
|
|
686
|
+
- **Green** (no `allowFailureJobs`) or **unknown**: silent (no banner) — informational only.
|
|
671
687
|
|
|
672
688
|
The banner is non-blocking — display in the Session Overview, do not halt the session. If `ci-status-banner.mjs` is absent (pre-#369 plugin install), skip silently.
|
|
673
689
|
|
|
@@ -707,6 +723,23 @@ Group issues by:
|
|
|
707
723
|
|
|
708
724
|
Non-blocking. Cross-reference: `scripts/lib/session-lock.mjs` (`readLock`, `DEFAULT_TTL_HOURS` — the current session's lock `started_at` is the self-exclusion cutoff), `scripts/backfill-abandoned-sessions.mjs` (the backfill CLI the message recommends) and issue #724.
|
|
709
725
|
|
|
726
|
+
**The backfill is mechanical since #926 — the banner's CLI hint is a fallback, not the primary path.** `hooks/on-session-start.mjs` calls `backfillOnSessionStart()` from `scripts/backfill-abandoned-sessions.mjs` on every SessionStart, which **applies** (writes) the reconstructed stubs rather than only previewing them. This decouples recovery from `/close`: `hooks/on-session-end.mjs` also backfills, but SessionEnd fires only on a REGULAR close, so a session killed by Ctrl-C, a timeout, or a crash left no ledger record until the NEXT clean close — which may never come (observed: this repo's ledger 18.9h behind events.jsonl across 8 commits). Running at start means the *next* session recovers the previous one, whatever killed it.
|
|
727
|
+
|
|
728
|
+
Four properties make that safe to run unattended on every start:
|
|
729
|
+
- **Idempotent.** Dedupe against sessions.jsonl plus an atomic `wx` marker file; repeated starts write nothing new. Synthetic ids are derived from the session's own `started_at` + a sha256 of its UUID, so they are stable across runs.
|
|
730
|
+
- **Self-excluding.** It runs BEFORE this session emits `orchestrator.session.started`, so the starting session is not a candidate at all. On a clear/compact/resume re-fire (where an earlier started-event *is* present) the core's `skipped-own-live-lock` guard catches it against the lock bootstrapped moments earlier.
|
|
731
|
+
- **Foreign-safe.** Lock ownership is evaluated against the CANDIDATE, not the running process: a candidate holding a live lock returns `skipped-own-live-lock` before the `relaxDeadByAge` (#731) relaxation is consulted. A running foreign session is therefore never recorded as `abandoned`. Residual, accepted: a live session that does NOT hold the lock (it lost the acquire race) AND has emitted no event for longer than `DEFAULT_TTL_HOURS` (4h) can still be relaxed past — a candidate the system's own liveness model already treats as dead.
|
|
732
|
+
- **Bounded + non-blocking.** Capped at `SESSION_START_LIMIT` (25) core calls, walked newest-first so the budget reaches the recent abandoned sessions rather than being spent on ancient already-recorded ones; **measured median 845ms** (5 steady-state runs: 713/835/845/921/984) on a 1.7MB events.jsonl / 187-candidate store, coordinator-verified 2026-07-30. Treat that as the cost this adds to every session start — it is roughly a second, not a rounding error, and it scales with the events ledger rather than the candidate count. Every failure is swallowed — a backfill error can never block a session start. Operator escape hatch: `SO_DISABLE_STARTUP_BACKFILL=1`.
|
|
733
|
+
|
|
734
|
+
When the run reports `truncated: true` (more candidates than the per-start budget), the remainder is picked up by subsequent starts; `node scripts/backfill-abandoned-sessions.mjs --dry-run` remains the way to inspect the full backlog, and `--apply` the way to drain it in one pass.
|
|
735
|
+
|
|
736
|
+
Additionally, invoke the sessions-integrity probe (`scripts/lib/sessions-integrity-banner.mjs`) via `checkSessionsIntegrity({ repoRoot })` (synchronous — no await). Where sessions-staleness above detects records that were never written, this detects records that WERE written but are schema-invalid — appended by a path that bypassed `scripts/emit-session.mjs` (which validates and would have refused). The loss is otherwise silent: `scripts/vault-mirror.mjs` reports such a record as `{"action":"skipped-invalid"}` on stdout and still exits 0, so the affected sessions simply have no vault note and nobody is told. Deliberately un-gated by Session Config (like `project-hygiene`) — a check nobody enables finds nothing. It returns `null` (silent no-op) when `.orchestrator/metrics/sessions.jsonl` is absent, empty, unreadable, or holds no parseable JSON line, and when every parseable record satisfies both validators; unparseable lines are skipped rather than reported (this probe judges schema integrity, not file corruption). The probe reports TWO populations, because measurement showed neither validator's failure set contains the other (this repo, 2026-07-31, 203 records: 3 vs 12, overlapping in only 2) — `validateSession()` treats `effectiveness` as optional while vault-mirror requires it, so reporting one alone would hide the other. The vault-mirror population is measured by invoking the real render path in a try/catch, never by re-deriving its field list. When a non-null result is returned (`{ severity, message, total, schemaInvalid, mirrorSkipped }`), render `result.message` alongside the other banners:
|
|
737
|
+
- **warn** (records fail `validateSession()` but all still mirror — corruption without loss): `"⚠ sessions-integrity: <N> of <M> records fail validateSession (<ids>) — records were appended without passing scripts/emit-session.mjs …"`
|
|
738
|
+
- **alert** (at least one record is dropped by vault-mirror — those sessions have no vault note right now): same message with a `🚨` prefix and an appended `"; <N> are dropped by vault-mirror as skipped-invalid — those sessions have NO vault note (<ids>)"` clause.
|
|
739
|
+
- **Fully valid ledger**: silent (no banner).
|
|
740
|
+
|
|
741
|
+
Non-blocking. Note the remedy is a re-emit of the affected records through `scripts/emit-session.mjs`, not an edit of the ledger by hand. Cross-reference: `scripts/lib/session-schema/validator.mjs` (`validateSession` — the canonical write-path schema), `scripts/lib/vault-mirror/render-sessions.mjs` (the render path whose throw becomes `skipped-invalid`), `skills/session-end/session-metrics-write.md` (the prose prohibition this banner backstops), `hooks/pre-bash-sessions-ledger-guard.mjs` (the write-guard half) and GitLab issue #958.
|
|
742
|
+
|
|
710
743
|
Additionally, invoke the owner-config probe (`scripts/lib/owner-config-banner.mjs`) via `checkOwnerConfig()` (synchronous — no await, no `repoRoot` argument: the probe reads the host-wide `owner.yaml`, not a per-repo file). The helper returns `null` (silent no-op) on a clean load, when `owner.yaml` is simply absent, or on any internal read/parse error. When a non-null result is returned (`{ severity: 'warn', message, droppedSections?, sectionWarnings?, discarded? }`), render `result.message` alongside the other banners:
|
|
711
744
|
- **Optional section(s) dropped to defaults** (`droppedSections` present): an OPTIONAL object section (`paths`, `dispatcher`) was malformed and replaced by its default value.
|
|
712
745
|
- **Whole file discarded** (`discarded: true`): a REQUIRED section (`owner`, `tone`, `efficiency`, `hardware-sharing`) was invalid, so the entire file was discarded and defaults are in effect.
|
|
@@ -726,7 +759,31 @@ Group issues by:
|
|
|
726
759
|
|
|
727
760
|
Non-blocking. Cross-reference: `scripts/lib/gitlab-portfolio/vcs-detect.mjs` (`discoverVaultRepos` — the canonical "registered" definition), `scripts/lib/config/context-coverage.mjs` (`_parseContextCoverage`), and issue #831.
|
|
728
761
|
|
|
729
|
-
|
|
762
|
+
Additionally, invoke the CLAUDE.md budget-lint probe (`scripts/lib/claude-md-budget-lint.mjs`) via `checkClaudeMdBudgetLint({ repoRoot })` (synchronous — no await). This is a **warn-only** probe — its result is rendered, never gated; the underlying `lintClaudeMd()`/CLI exit-code contract (0/1/2, `--mode hard` by default) belongs to the standalone bootstrap-time lint (`skills/bootstrap/SKILL.md` § Step 2c) and is NEVER invoked here. The helper returns `null` (silent no-op) when no CLAUDE.md/AGENTS.md resolves under `repoRoot`, when the resolved file has zero violations, or on any read/parse failure. When a non-null result is returned (`{ severity: 'warn', message }`), render `result.message` alongside the other banners:
|
|
763
|
+
- **Violations found**: `"⚠ CLAUDE.md budget lint: <N> violation(s) (<rule names>) in <file> — run \`node scripts/lib/claude-md-budget-lint.mjs --mode warn\` for details."` — `<rule names>` is the de-duplicated set of violated rule ids (`max-lines`, `max-line-chars`, `provenance-header`) present in the file.
|
|
764
|
+
- **Clean file / no instruction file**: silent (no banner).
|
|
765
|
+
|
|
766
|
+
Non-blocking. Cross-reference: `scripts/lib/instruction-budget-guard.mjs` (sibling directive-COUNT probe over `.claude/rules/*.md` — this probe measures raw-file PROPERTIES of CLAUDE.md/AGENTS.md itself, a distinct dimension) and issue #878 (FA2b).
|
|
767
|
+
|
|
768
|
+
Additionally, invoke the tests:src-ratio probe (`scripts/lib/tests-src-ratio.mjs`) via `checkTestsSrcRatio({ repoRoot })` (synchronous — no await). It returns `null` (silent no-op) when the ratio is inside the TV-003 corridor, when `repoRoot` is missing, or on any measurement failure. When a non-null result is returned (`{ severity: 'warn', message, ratio, ceiling }`), render `result.message` alongside the other banners.
|
|
769
|
+
|
|
770
|
+
**Why this is a banner and not a gate.** `.claude/rules/test-value.md` § TV-003 names the ceiling as the trigger for a CONSOLIDATION wave — the rule's operative instrument. Before this wiring the trigger fired into a void: the only references were two rule files asking a human to run the command, so the condition could be true for months with nothing saying so (it was true, at 1.70, on the commit that introduced the script). The rule's refusal of a bidirectional ratchet stands unchanged — this surfaces the trigger, it does not block on it. `--check` remains deliberately unwired from CI.
|
|
771
|
+
|
|
772
|
+
Non-blocking. Cross-reference: `.claude/rules/test-value.md` § TV-003 (the corridor rule and why a ratchet was rejected), `.claude/rules/testing.md` § Coverage Enforcement (the 70% floor that binds independently), and issue #930.
|
|
773
|
+
|
|
774
|
+
Additionally, invoke the project-hygiene probe family (`scripts/lib/project-hygiene.mjs`) via `checkProjectHygiene({ repoRoot })` (synchronous — no await). **This is the only probe in Phase 4 besides `ci-status` that inspects the PROJECT rather than the orchestrator's own substrate** — every other probe above measures vault, peer-cards, loop readiness, instruction budget, or this tool's own ledger. It is deliberately NOT config-gated: a hygiene check nobody enables finds nothing, which is how the equivalent coverage was lost before (see `skills/session-end/discovery-scan.md` — the discovery scan defaults OFF for exactly the `housekeeping` session type that most needs it).
|
|
775
|
+
|
|
776
|
+
The helper returns `null` (silent no-op) when `repoRoot` is missing/non-string, when the path is not a git repository, or when every check passes. When a non-null result is returned (`{ severity: 'warn', message, findings, mechanical }`), render `result.message` alongside the other banners:
|
|
777
|
+
- **Findings present**: render the message verbatim. It already leads with the count and the mechanically-fixable subset, then names the top 3 and summarises the remainder — this shape was chosen because a flat list stops being read past roughly 25 findings.
|
|
778
|
+
- **Healthy repo**: silent (no banner).
|
|
779
|
+
|
|
780
|
+
Use `result.mechanical` when proposing session scope: findings with `fixable: true` (aged artifacts, ignored ballast, a missing CI audit step) are safe batch work, while the rest (release cadence, absent CI, undocumented configuration) need an operator decision and belong in the Q&A, not in an auto-fix batch.
|
|
781
|
+
|
|
782
|
+
The checks are: release-tag/CHANGELOG distance from HEAD, ignored working-tree ballast plus files that are neither tracked nor ignored, aged `.orchestrator/` artifacts, CI pipeline presence and dependency-audit coverage, and `.env.example` presence. Two high-yield checks are intentionally NOT here: **docs-drift** is already covered by `claude-md-drift-check` (it only runs at session-END, so the gap is scheduling, not implementation), and **env completeness** is omitted because diffing `process.env` reads against `.env.example` produced a 100% false-positive rate against code that reads configuration through a central schema module.
|
|
783
|
+
|
|
784
|
+
Non-blocking. Cross-reference: `scripts/lib/ci-status-banner.mjs` (the sibling project-facing probe) and `.claude/rules/test-value.md` § TV-005 (why structural gates beat unit-test volume).
|
|
785
|
+
|
|
786
|
+
All banners are non-blocking — display in the Session Overview, do not halt the session. If `bootstrap-lock-freshness.mjs` is absent (pre-#186 plugin install) or `peer-cards/staleness-banner.mjs` is absent (pre-#503 plugin install) or `loop-readiness-banner.mjs` is absent (pre-#633 plugin install) or `instruction-budget-guard.mjs` is absent (pre-#687 plugin install) or `reconcile-nudge-banner.mjs` is absent (pre-#723 plugin install) or `sessions-staleness-banner.mjs` is absent (pre-#724 plugin install) or `sessions-integrity-banner.mjs` is absent (pre-#958 plugin install) or `owner-config-banner.mjs` is absent (pre-#820 plugin install) or `moc-staleness-banner.mjs` / `context-coverage-banner.mjs` are absent (pre-#831 plugin install) or `claude-md-budget-lint.mjs` is absent (pre-#878 plugin install), skip silently.
|
|
730
787
|
|
|
731
788
|
## Phase 4.5: Resource Health (v3.1.0)
|
|
732
789
|
|
|
@@ -1028,6 +1085,15 @@ Cross-reference: GitLab #845 (Epic #841); `docs/prd/2026-07-20-anonymous-usage-t
|
|
|
1028
1085
|
- Focus on git cleanup, documentation currency, CI health
|
|
1029
1086
|
- Skip deep research — prioritize operational tasks
|
|
1030
1087
|
- Run token efficiency check: `bash "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/token-audit.sh"` and include findings in Session Overview. Flag any HIGH/WARN items as recommended housekeeping tasks.
|
|
1088
|
+
- **Run the drift check as a work-list, not as a gate:**
|
|
1089
|
+
```bash
|
|
1090
|
+
node "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/skills/claude-md-drift-check/checker.mjs" --mode warn
|
|
1091
|
+
```
|
|
1092
|
+
`--mode warn` always exits 0 and returns findings as JSON — it must never block session-start. Summarise `errors[]` and `warnings[]` by check name in the Session Overview and offer them as candidate scope in the Phase 8 Q&A.
|
|
1093
|
+
|
|
1094
|
+
**Why here and not only at close.** The same checker already runs at session-end (`skills/session-end/SKILL.md` Phase 2), where it verifies the work just done. That is the wrong moment to *discover* drift: doc-vs-reality drift was the single most frequently confirmed finding in the six-repo diagnostic run (6 of 6 repos), and a housekeeping session that only learns about it at close cannot act on it. Running it at the start turns it into the session's work-list. It is deliberately scoped to `housekeeping` — for `feature`/`deep` sessions this list is a distraction from the agreed scope, and the close-time run still covers them.
|
|
1095
|
+
|
|
1096
|
+
**Read the output critically.** In a consumer repo the checker reported 69 errors of which zero concerned that repo — all were dangling `## See Also` citations inside vendored, never-curated baseline rule copies. Before proposing any of it as scope, check whether a finding points at repo-owned content or at vendored files; report the split rather than the raw count.
|
|
1031
1097
|
|
|
1032
1098
|
## Phase 7.1: Issue Premise Verification (#730/H3)
|
|
1033
1099
|
|
|
@@ -10,7 +10,10 @@ Read `.orchestrator/host.json` (written by `hooks/on-session-start.mjs`) and run
|
|
|
10
10
|
// Conceptual — the wave-executor and session-plan skills call these directly.
|
|
11
11
|
import { probe, evaluate } from '$PLUGIN_ROOT/scripts/lib/resource-probe.mjs';
|
|
12
12
|
const snapshot = await probe();
|
|
13
|
-
const verdict = evaluate(snapshot, config['resource-thresholds']
|
|
13
|
+
const verdict = evaluate(snapshot, config['resource-thresholds'], {
|
|
14
|
+
heavyRepo: config['heavy-repo'],
|
|
15
|
+
agentsPerWave: config['agents-per-wave'],
|
|
16
|
+
});
|
|
14
17
|
```
|
|
15
18
|
|
|
16
19
|
The `evaluate()` result has three fields:
|
|
@@ -18,16 +21,20 @@ The `evaluate()` result has three fields:
|
|
|
18
21
|
- `reasons`: array of human-readable explanations
|
|
19
22
|
- `recommended_agents_per_wave_cap`: integer cap (0 = coordinator-direct) or null
|
|
20
23
|
|
|
24
|
+
The third `options` argument is optional (HR-003/HR-004, baseline #60) — when `config['heavy-repo']` is `true`, the cap is forced to at most `config['agents-per-wave']` regardless of the live verdict (static preflight ceiling; more-restrictive-wins against whatever the resource signals already computed). Omitting `options` entirely preserves pre-#60 behaviour.
|
|
25
|
+
|
|
21
26
|
## Adaptive Rules (default thresholds; configurable via `resource-thresholds`)
|
|
22
27
|
|
|
23
28
|
| Signal | Threshold | Action |
|
|
24
29
|
|--------|-----------|--------|
|
|
25
30
|
| RAM free below `ram-free-min-gb` (default 4) | warn | Cap `agents-per-wave` at 2 |
|
|
26
31
|
| RAM free below `ram-free-critical-gb` (default 2) | critical | Recommend coordinator-direct (0 agents) |
|
|
27
|
-
| CPU load above `cpu-load-max-pct` (default 80) sustained | warn | Cap `agents-per-wave` at 2 |
|
|
32
|
+
| CPU load above `cpu-load-max-pct` (default 80) sustained — judged on **min(1m, 5m)** load average (#943) | warn | Cap `agents-per-wave` at 2 |
|
|
28
33
|
| Claude processes ≥ `concurrent-sessions-warn` (default 5) | warn | Warn; suggest sequencing or waiting |
|
|
29
34
|
| SSH session detected AND `ssh-no-docker: true` | info | Append note: host is SSH-attached, Docker-dependent steps should run on a local dev host |
|
|
30
35
|
|
|
36
|
+
**CPU methodology (#943):** the gate/probe runs right after the coordinator's own CPU-saturating quality-gate run by construction, so the 1-minute load average systematically carries that decaying tail (observed: 96% → 75% within 36s). `probe()` therefore also emits `cpu_load_5m` / `cpu_load_5m_pct`, and `evaluate()` + `evaluateWaveResourceGate()` judge the CPU threshold on **min(1m, 5m)**: only-1m-high is reported as an informational "decaying transient" reason without capping; both-high (genuine sustained load) still caps. When `cpu_load_5m_pct` is `null` (Windows, zero-load), judging falls back to the 1m-derived `cpu_load_pct` alone.
|
|
37
|
+
|
|
31
38
|
## Presentation
|
|
32
39
|
|
|
33
40
|
Print a one-line Resource Health verdict immediately after Phase 4's output:
|
|
@@ -36,6 +43,12 @@ Print a one-line Resource Health verdict immediately after Phase 4's output:
|
|
|
36
43
|
Resource Health: ⚠ warn — RAM free 3.1 GB below threshold 4 GB; capping agents-per-wave at 2.
|
|
37
44
|
```
|
|
38
45
|
|
|
46
|
+
When `config['heavy-repo']` is `true` and the HR-004 preflight ceiling actually reduces `recommended_agents_per_wave_cap` below what the live verdict alone would have produced, print an additional banner line right after the verdict line:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
⚠ Heavy-repo mode active — agents-per-wave capped to 4 (Session Config heavy-repo: true)
|
|
50
|
+
```
|
|
51
|
+
|
|
39
52
|
When verdict is `warn` or `critical`, use the AskUserQuestion tool to present:
|
|
40
53
|
1. **Proceed as recommended** (apply the cap) — Recommended
|
|
41
54
|
2. **Proceed as originally planned** (user accepts the risk)
|
|
@@ -201,8 +201,8 @@ If the glab query fails, log the error and proceed with an empty fingerprint set
|
|
|
201
201
|
|
|
202
202
|
| Severity | Action |
|
|
203
203
|
|----------|--------|
|
|
204
|
-
| `critical` | Auto-create issue — no AUQ. Label: `from:test-runner,priority
|
|
205
|
-
| `high` | Auto-create issue — no AUQ. Label: `from:test-runner,priority
|
|
204
|
+
| `critical` | Auto-create issue — no AUQ. Label: `from:test-runner,priority::critical` |
|
|
205
|
+
| `high` | Auto-create issue — no AUQ. Label: `from:test-runner,priority::high` |
|
|
206
206
|
| `medium` | Batched AUQ triage (see below) |
|
|
207
207
|
| `low` | Batched AUQ triage (see below) |
|
|
208
208
|
|
|
@@ -62,6 +62,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
62
62
|
import { z } from 'zod';
|
|
63
63
|
import YAML from 'yaml';
|
|
64
64
|
import { resolveInstructionFile } from '../../scripts/lib/common.mjs';
|
|
65
|
+
import { findSessionConfigBlock } from '../../scripts/lib/config/section-extractor.mjs';
|
|
65
66
|
import {
|
|
66
67
|
computeSchemaHash,
|
|
67
68
|
writeBaseline,
|
|
@@ -296,18 +297,38 @@ function isExcluded(relPath) {
|
|
|
296
297
|
// Returns true if dir contains at least one recognized vault marker:
|
|
297
298
|
// 1. _meta/ directory
|
|
298
299
|
// 2. CLAUDE.md or AGENTS.md (alias — see skills/_shared/instruction-file-resolution.md)
|
|
299
|
-
//
|
|
300
|
-
// file is resolved via resolveInstructionFile() so CLAUDE.md
|
|
301
|
-
// AGENTS.md is accepted on Codex CLI repos.
|
|
300
|
+
// whose `## Session Config` block declares a `vault-sync:` key. The
|
|
301
|
+
// instruction file is resolved via resolveInstructionFile() so CLAUDE.md
|
|
302
|
+
// wins ties and AGENTS.md is accepted on Codex CLI repos.
|
|
302
303
|
// 3. .obsidian/ directory
|
|
304
|
+
//
|
|
305
|
+
// Marker 2 was two whole-file substring tests until #968:
|
|
306
|
+
// content.includes('## Session Config') && content.includes('vault-sync:')
|
|
307
|
+
// A substring test is the wrong instrument for "does this directory look like
|
|
308
|
+
// a vault" in three independent ways, all reachable:
|
|
309
|
+
// (a) `'### Session Config'.includes('## Session Config')` is TRUE (from
|
|
310
|
+
// index 1), so an H3 — or any deeper heading — passed the marker.
|
|
311
|
+
// HTML comment matched. Any document ABOUT session-orchestrator config
|
|
312
|
+
// reads as a vault marker.
|
|
313
|
+
// (b) A `## Session Config` mention in ordinary prose (not at line start)
|
|
314
|
+
// matched, e.g. "see the ## Session Config block" mid-sentence.
|
|
315
|
+
// (c) `vault-sync:` was accepted ANYWHERE in the file — a prose sentence or
|
|
316
|
+
// a doc-comment sufficed; it never had to be a config key, let alone one
|
|
317
|
+
// inside the Session Config block.
|
|
318
|
+
// Misclassifying a directory as a vault is not cosmetic: it makes the
|
|
319
|
+
// validator crawl and enforce vault frontmatter over an arbitrary tree, and
|
|
320
|
+
// (via the cwd branch below) silently adopt it as VAULT_DIR.
|
|
321
|
+
//
|
|
322
|
+
// The fix uses the SSOT block extractor, so the heading must be a real
|
|
323
|
+
// heading LINE and `vault-sync:` must be a key INSIDE that block.
|
|
303
324
|
function isVaultDir(dir) {
|
|
304
325
|
if (existsSync(join(dir, '_meta')) && statSync(join(dir, '_meta')).isDirectory()) return true;
|
|
305
326
|
if (existsSync(join(dir, '.obsidian')) && statSync(join(dir, '.obsidian')).isDirectory()) return true;
|
|
306
327
|
const instr = resolveInstructionFile(dir);
|
|
307
328
|
if (instr) {
|
|
308
329
|
try {
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
330
|
+
const block = findSessionConfigBlock(readFileSync(instr.path, 'utf8'));
|
|
331
|
+
if (block && /^\s*(?:-\s+)?(?:\*\*)?vault-sync(?:\*\*)?\s*:/m.test(block.body)) return true;
|
|
311
332
|
} catch {
|
|
312
333
|
// unreadable instruction file — not a vault marker
|
|
313
334
|
}
|
|
@@ -446,7 +467,85 @@ function parseFrontmatter(raw) {
|
|
|
446
467
|
}
|
|
447
468
|
|
|
448
469
|
// ── Wiki-link regex — captures link body for target parsing ─────────────────
|
|
449
|
-
|
|
470
|
+
// NOTE: intentionally NOT module-level. A shared `/g` regex driven by
|
|
471
|
+
// `.exec()` in a loop carries `lastIndex` state across calls; the previous
|
|
472
|
+
// module-level `WIKILINK_RE` was safe only because the loop always ran to
|
|
473
|
+
// exhaustion (never an early return/continue). Constructing a fresh regex
|
|
474
|
+
// per `extractWikiLinks()` call removes that hazard structurally rather than
|
|
475
|
+
// relying on "the loop happens to always finish" (#852).
|
|
476
|
+
function wikilinkRegex() {
|
|
477
|
+
return /\[\[([^\]]+)\]\]/g;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ── Code-span stripping (#852, hardened post-review) ────────────────────────
|
|
481
|
+
// A wikilink written as inline code (`` `[[target]]` ``) or inside a fenced /
|
|
482
|
+
// indented code block is pedagogical prose ABOUT the Obsidian wiki-link
|
|
483
|
+
// convention (#159 pattern: keep named invariants in backticks), not a real
|
|
484
|
+
// link, and must never surface a dangling-wiki-link warning. All span kinds
|
|
485
|
+
// are blanked out (non-newline chars -> space, newlines preserved) BEFORE the
|
|
486
|
+
// wikilink regex runs, so their `[[...]]` content is invisible to
|
|
487
|
+
// extractWikiLinks() while line count / offsets stay stable for any future
|
|
488
|
+
// position-aware consumer.
|
|
489
|
+
//
|
|
490
|
+
// NOTE: intentionally functions, not module-level `/g` regex constants. Every
|
|
491
|
+
// call below builds a fresh RegExp so no shared `lastIndex` state can leak
|
|
492
|
+
// across calls (same rationale as wikilinkRegex() above, #852).
|
|
493
|
+
//
|
|
494
|
+
// Order: fenced blocks (multi-line) -> indented code blocks (line-based) ->
|
|
495
|
+
// inline spans (single/double backtick, same line only). Fenced blocks run
|
|
496
|
+
// first because they are the only multi-line shape; the other two are
|
|
497
|
+
// line-local and their relative order does not change the result (a line
|
|
498
|
+
// already blanked by an earlier pass is all-spaces and matches idempotently
|
|
499
|
+
// under either later pass).
|
|
500
|
+
//
|
|
501
|
+
// Fenced-block fix (post-#852 QA review): the ORIGINAL `/```[\s\S]*?```/g`
|
|
502
|
+
// paired ANY two ``` runs in the whole file — including a ``` merely
|
|
503
|
+
// MENTIONED mid-sentence in prose — which silently blanked (and hid dangling
|
|
504
|
+
// links inside) everything between an unrelated mention and the next real
|
|
505
|
+
// fence. Real CommonMark fences must open at the START of a line (up to 3
|
|
506
|
+
// leading spaces); this regex is now line-anchored so a mid-line mention can
|
|
507
|
+
// never open a fence. The closing fence must reuse the SAME fence character
|
|
508
|
+
// (backtick vs tilde) with a run of at least 3 — `(?:\1){3,}` repeats the
|
|
509
|
+
// single captured fence character, so a backtick fence cannot be closed by a
|
|
510
|
+
// tilde run or vice versa. Also handles `~~~` fences and fenced blocks that
|
|
511
|
+
// carry an info string (e.g. ```` ```md ````) — the info string is just the
|
|
512
|
+
// rest of the opening line, already consumed by `[^\n]*`.
|
|
513
|
+
function fencedCodeRegex() {
|
|
514
|
+
return /^ {0,3}(`|~)\1{2,}[^\n]*\n[\s\S]*?^ {0,3}(?:\1){3,}[ \t]*$/gm;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Indented code blocks (4+ leading spaces, or a leading tab) are CommonMark
|
|
518
|
+
// code too. Required to be flanked by a blank line (or start/end of text) on
|
|
519
|
+
// both sides — mirrors CommonMark's "separated from surrounding paragraph
|
|
520
|
+
// text" rule closely enough to avoid blanking ordinary 4-space-indented list
|
|
521
|
+
// continuation text that isn't actually a code block, while still catching
|
|
522
|
+
// the FP shape this fixes.
|
|
523
|
+
function indentedCodeRegex() {
|
|
524
|
+
return /(?<=^|\n\n)(?: {4,}|\t)[^\n]*(?:\n(?: {4,}|\t)[^\n]*)*/g;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Inline code spans: single-backtick (`` ` `` ... `` ` ``) or double-backtick
|
|
528
|
+
// (`` `` `` ... `` `` ``, used when the wrapped content itself contains a
|
|
529
|
+
// backtick) delimiters, same line only. The closing delimiter must be the
|
|
530
|
+
// SAME LENGTH as the opening one (`\1` backreference on the captured 1-2
|
|
531
|
+
// backtick run) so a double-backtick span isn't mis-closed by the first lone
|
|
532
|
+
// backtick inside its own content — the over-strip hazard called out in
|
|
533
|
+
// #852. A lone/stray backtick with no same-length partner on the same line
|
|
534
|
+
// therefore matches nothing and is left as plain text.
|
|
535
|
+
function inlineCodeRegex() {
|
|
536
|
+
return /(`{1,2})[^\n]*?\1(?!`)/g;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function blank(match) {
|
|
540
|
+
return match.replace(/[^\n]/g, ' ');
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function stripCodeSpans(text) {
|
|
544
|
+
let out = text.replace(fencedCodeRegex(), blank);
|
|
545
|
+
out = out.replace(indentedCodeRegex(), blank);
|
|
546
|
+
out = out.replace(inlineCodeRegex(), blank);
|
|
547
|
+
return out;
|
|
548
|
+
}
|
|
450
549
|
|
|
451
550
|
function findAliasSeparatorIndex(linkBody) {
|
|
452
551
|
for (let i = 0; i < linkBody.length; i++) {
|
|
@@ -463,9 +562,11 @@ function extractWikiLinkTarget(linkBody) {
|
|
|
463
562
|
}
|
|
464
563
|
|
|
465
564
|
function extractWikiLinks(content) {
|
|
565
|
+
const stripped = stripCodeSpans(content);
|
|
466
566
|
const targets = new Set();
|
|
567
|
+
const re = wikilinkRegex();
|
|
467
568
|
let m;
|
|
468
|
-
while ((m =
|
|
569
|
+
while ((m = re.exec(stripped)) !== null) {
|
|
469
570
|
const target = extractWikiLinkTarget(m[1]);
|
|
470
571
|
if (target.length > 0) targets.add(target);
|
|
471
572
|
}
|
|
@@ -246,7 +246,7 @@ Each agent prompt MUST include:
|
|
|
246
246
|
2. **Full context**: file paths, current code structure, issue description. If a bite-sized executable plan exists at `docs/plans/<feature>.md` for the wave's tasks (see `skills/write-executable-plan/SKILL.md`), include the path in each agent's prompt and instruct the agent to follow the plan's 5-step structure verbatim.
|
|
247
247
|
3. **Acceptance criteria**: measurable definition of done
|
|
248
248
|
4. **Rule references**: the wave's applicable rules are injected automatically as the `<APPLICABLE-RULES>` block produced by `scripts/print-applicable-rules.mjs` (see `wave-loop.md` § "Pre-Dispatch: Glob-Scoped Rule Injection (#336/#694)"). The block is computed once per wave from the wave's `allowedPaths` and prepended to every agent prompt — do not hand-copy rule paths into the prompt.
|
|
249
|
-
5. **Testing expectation
|
|
249
|
+
5. **Testing expectation** (need-gated): "Before writing any test, name the concrete bug a NEW test would catch that the existing suite does not. No nameable bug → write NO test and report `no-tests-needed: <reason>` — that is a SUCCESS outcome, not a gap. With a nameable bug: exactly one test for it. Running existing tests is always mandatory."
|
|
250
250
|
6. **Commit instruction**: "Do NOT commit. The coordinator handles commits."
|
|
251
251
|
7. **Turn limit**: Include the maxTurns instruction from `circuit-breaker.md`
|
|
252
252
|
8. **Verification before completion**: Before claiming any task done, run the verification command and quote the evidence inline. See `.claude/rules/verification-before-completion.md`.
|
|
@@ -272,10 +272,13 @@ During this wave, you may propose a learning to the session's memory via the CLI
|
|
|
272
272
|
--subject "one-line title (max 100 chars, no newlines)" \
|
|
273
273
|
--insight "your discovery paragraph (max 2000 chars)" \
|
|
274
274
|
--evidence "concrete proof: code citation / log excerpt / commit ref (max 5000 chars)" \
|
|
275
|
-
--confidence <0.5 to 1.0>
|
|
275
|
+
--confidence <0.5 to 1.0> \
|
|
276
|
+
--file-paths "scripts/lib/a.mjs,scripts/lib/b.mjs"
|
|
276
277
|
|
|
277
278
|
MUST prefix with `SO_WAVE_AGENT=1` — without it the CLI returns exit 3 `rejected-wrong-context`. The env-var is the per-process guard that distinguishes wave-executor agents from coordinator-context invocations.
|
|
278
279
|
|
|
280
|
+
`--file-paths` is optional but strongly encouraged: repo-relative path(s) this learning applies to (repeatable and/or comma-separated, deduped; rejects absolute paths, `..` segments, embedded newlines, entries over 256 chars, and more than 20 entries). Without `--file-paths` this learning can never become `/reconcile`-eligible — the reconciliation engine can only convert a learning into a conditional `.claude/rules/*.md` rule when it carries a non-empty scope (issue #900).
|
|
281
|
+
|
|
279
282
|
Exit code 0 = queued (the coordinator will present at session-end via AskUserQuestion); 1 = quota-exceeded; 2 = rejected-low-confidence (below floor 0.5); 3 = rejected-wrong-context (STATE.md not active OR SO_WAVE_AGENT != "1"); 4 = error (arg validation or internal).
|
|
280
283
|
|
|
281
284
|
Use ONLY when you find a recurring pattern, anti-pattern, or constraint worth carrying into future sessions. The coordinator confirms each proposal before it lands in learnings.jsonl. Do NOT over-propose — quota is bounded per wave.
|