session-orchestrator 5.2.0 → 5.3.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/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +5 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +5 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +5 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
- package/.agents/skills/close/SKILL.md +5 -1
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +5 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +5 -1
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +5 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +5 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +6 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +6 -2
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +5 -1
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +5 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +5 -1
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +5 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +5 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +5 -1
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +5 -1
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +5 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +5 -1
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +5 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +5 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +5 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +5 -1
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +5 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/evolve/SKILL.md +1 -1
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/session.md +1 -1
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +12 -3
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +136 -0
- package/README.md +9 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +7 -3
- package/docs/baseline.md +12 -6
- package/docs/codex-setup.md +14 -2
- package/docs/components.md +7 -5
- package/docs/events-schema.md +56 -9
- package/docs/rule-authoring.md +58 -6
- package/docs/session-config-reference.md +100 -7
- package/docs/session-config-template.md +31 -2
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +85 -8
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +58 -23
- package/hooks/on-session-start.mjs +48 -11
- package/hooks/on-stop.mjs +168 -22
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +3 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +200 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +15 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +16 -11
- package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
- package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-task-scope-disjoint.mjs +233 -2
- package/hooks/subagent-telemetry.mjs +15 -19
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +1 -1
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/session.md +1 -1
- package/rules/README.md +1 -1
- package/rules/opt-in-domain/prompt-caching.md +1 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +3 -3
- package/rules/opt-in-stack/frontend.md +1 -1
- package/rules/opt-in-stack/security-web.md +3 -3
- package/rules/opt-in-stack/swift.md +1 -1
- package/scripts/autopilot.mjs +23 -2
- package/scripts/backfill-abandoned-sessions.mjs +117 -15
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +75 -29
- package/scripts/eval-session.mjs +65 -3
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-cursor-adapter.mjs +61 -16
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/autopilot/flags.mjs +12 -1
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +14 -1
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/command-blocker.mjs +275 -28
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/reaper.mjs +162 -0
- package/scripts/lib/config.mjs +14 -0
- package/scripts/lib/convergence-monitor.mjs +74 -11
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +524 -5
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +27 -2
- package/scripts/lib/gates/gate-full.mjs +28 -3
- package/scripts/lib/gates/gate-helpers.mjs +243 -21
- package/scripts/lib/gates/gate-incremental.mjs +28 -3
- package/scripts/lib/gates/gate-per-file.mjs +27 -2
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +146 -4
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/maintenance-due-banner.mjs +53 -88
- package/scripts/lib/orphan-reaper.mjs +1588 -0
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/process-group.mjs +899 -0
- package/scripts/lib/quality-gate.mjs +107 -28
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/rule-loader.mjs +41 -12
- package/scripts/lib/scope-echo.mjs +39 -2
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +33 -6
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-schema/constants.mjs +30 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/sessions-canonical.mjs +23 -0
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
- package/scripts/lib/user-invocable-skills.mjs +23 -3
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
- package/scripts/lib/validate/check-entry-guard.mjs +13 -50
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +9 -1
- package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
- package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
- package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
- package/scripts/lib/validate/check-unwired-features.mjs +130 -27
- package/scripts/lib/validate/check-validator-registration.mjs +34 -10
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +4 -3
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +31 -7
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/release-session-lock.mjs +305 -0
- package/scripts/release.mjs +30 -5
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-quality-gate.mjs +156 -17
- package/scripts/sweep-expired-rules.mjs +14 -3
- package/scripts/validate-plugin.mjs +12 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-mirror.mjs +9 -1
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/autopilot/SKILL.md +22 -7
- package/skills/claude-md-drift-check/SKILL.md +1 -1
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/eval/SKILL.md +39 -24
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +1 -1
- package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +11 -0
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/session-metrics-write.md +10 -0
- package/skills/session-plan/SKILL.md +17 -5
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/references/phase-4-ssot-environment-check.md +2 -1
- package/skills/ux-grill/SKILL.md +1 -1
- package/skills/wave-executor/SKILL.md +8 -4
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +5 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +2 -1
- package/.codex-plugin/skills/convergence-monitoring/agents/openai.yaml +0 -5
- package/.codex-plugin/skills/npm-publish/agents/openai.yaml +0 -5
- package/.cursor/commands/convergence-monitoring.md +0 -13
- package/.cursor/commands/npm-publish.md +0 -13
- package/pi/prompts/convergence-monitoring.md +0 -11
- package/pi/prompts/npm-publish.md +0 -11
|
@@ -104,26 +104,37 @@
|
|
|
104
104
|
* @property {string} [error] - present only when the never-throws top-level guard fired.
|
|
105
105
|
*/
|
|
106
106
|
|
|
107
|
-
import {
|
|
108
|
-
import { isAbsolute
|
|
107
|
+
import { homedir } from 'node:os';
|
|
108
|
+
import { isAbsolute } from 'node:path';
|
|
109
109
|
|
|
110
110
|
import { expandTilde } from '../common.mjs';
|
|
111
111
|
import { learningKeyOf } from '../learnings/kebab.mjs';
|
|
112
|
-
import {
|
|
112
|
+
import {
|
|
113
|
+
countReconcileBacklog,
|
|
114
|
+
defaultLoadCandidatesForDedupe,
|
|
115
|
+
defaultLoadLearnings,
|
|
116
|
+
defaultReadMaterializedProvenance,
|
|
117
|
+
partitionMaterialized,
|
|
118
|
+
} from './backlog.mjs';
|
|
113
119
|
import { filterEligible } from './eligibility.mjs';
|
|
114
120
|
import { toActivationMetadata } from './emitter.mjs';
|
|
115
121
|
import { renderRule } from './renderer.mjs';
|
|
116
122
|
import {
|
|
117
123
|
DEFAULT_STORE_PATH,
|
|
118
124
|
buildCandidate,
|
|
119
|
-
isProcessed,
|
|
120
125
|
makeCandidateId,
|
|
121
|
-
loadCandidates as realLoadCandidates,
|
|
122
126
|
mergeCandidates as realMergeCandidates,
|
|
123
127
|
} from './idempotency.mjs';
|
|
124
128
|
|
|
125
|
-
/**
|
|
126
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Re-exported from the LEAF module `./backlog.mjs` (which now owns the
|
|
131
|
+
* side-effect-free backlog half) so every existing importer of these two names
|
|
132
|
+
* keeps working unchanged. A read-only probe should import them from
|
|
133
|
+
* `./backlog.mjs` DIRECTLY — importing them from here drags this orchestrator's
|
|
134
|
+
* whole closure (emitter, renderer, unicode validator, candidate-store writer)
|
|
135
|
+
* into the probe. See that module's header for the measured cost.
|
|
136
|
+
*/
|
|
137
|
+
export { countReconcileBacklog, partitionMaterialized };
|
|
127
138
|
|
|
128
139
|
/**
|
|
129
140
|
* Default volume brake (issue #900 D) — mirrors the `reconcile.max-proposals-
|
|
@@ -157,57 +168,6 @@ function zeroedResult(error) {
|
|
|
157
168
|
return result;
|
|
158
169
|
}
|
|
159
170
|
|
|
160
|
-
/**
|
|
161
|
-
* Default learnings loader — read + parse `<repoRoot>/.orchestrator/metrics/learnings.jsonl`
|
|
162
|
-
* line-by-line, migrate/normalize records through the learnings schema SSOT,
|
|
163
|
-
* and skip blank/malformed lines. A missing file (ENOENT) yields `[]`
|
|
164
|
-
* silently; an unreadable one (EACCES/EISDIR/…) yields `[]` with a stderr
|
|
165
|
-
* WARN (#1210 — ENOENT and other read failures are different facts, same
|
|
166
|
-
* split as `sessions-canonical.mjs` `readCanonicalSessions`).
|
|
167
|
-
*
|
|
168
|
-
* @param {string|undefined} repoRoot
|
|
169
|
-
* @returns {Array<Record<string, unknown>>}
|
|
170
|
-
*/
|
|
171
|
-
function defaultLoadLearnings(repoRoot) {
|
|
172
|
-
const root = typeof repoRoot === 'string' && repoRoot.length > 0 ? repoRoot : process.cwd();
|
|
173
|
-
const absPath = isAbsolute(DEFAULT_LEARNINGS_PATH)
|
|
174
|
-
? DEFAULT_LEARNINGS_PATH
|
|
175
|
-
: join(root, DEFAULT_LEARNINGS_PATH);
|
|
176
|
-
|
|
177
|
-
let raw;
|
|
178
|
-
try {
|
|
179
|
-
raw = readFileSync(absPath, 'utf8');
|
|
180
|
-
} catch (err) {
|
|
181
|
-
if (!err || err.code !== 'ENOENT') {
|
|
182
|
-
process.stderr.write(
|
|
183
|
-
`⚠ defaultLoadLearnings: cannot read ${absPath} ` +
|
|
184
|
-
`(${err?.code ?? '?'}: ${err?.message ?? String(err)}) — ` +
|
|
185
|
-
'treating as EMPTY, counts below are floors\n',
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
return [];
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** @type {Array<Record<string, unknown>>} */
|
|
192
|
-
const records = [];
|
|
193
|
-
for (const line of raw.split('\n')) {
|
|
194
|
-
const trimmed = line.trim();
|
|
195
|
-
if (trimmed.length === 0) continue;
|
|
196
|
-
let parsed;
|
|
197
|
-
try {
|
|
198
|
-
parsed = JSON.parse(trimmed);
|
|
199
|
-
} catch {
|
|
200
|
-
continue; // skip malformed line
|
|
201
|
-
}
|
|
202
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
203
|
-
records.push(
|
|
204
|
-
/** @type {Record<string, unknown>} */ (normalizeLearning(migrateLegacyLearning(parsed))),
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return records;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
171
|
/**
|
|
212
172
|
* Best-effort logical key for a REJECTED learning (rejections never run the
|
|
213
173
|
* emitter, so there is no `metadata.learningKey`). Delegates to the shared
|
|
@@ -352,109 +312,6 @@ export function resolveEffectiveTargets({ targets, baselineRoot } = {}) {
|
|
|
352
312
|
};
|
|
353
313
|
}
|
|
354
314
|
|
|
355
|
-
/**
|
|
356
|
-
* Default sidecar-candidate loader for the issue #484 idempotency dedupe
|
|
357
|
-
* check (below, step 3a). Deliberately gated on `repoRoot` being a
|
|
358
|
-
* caller-supplied, non-empty string — UNLIKE `defaultLoadLearnings` and
|
|
359
|
-
* `realMergeCandidates`, this does NOT fall back to `process.cwd()` when
|
|
360
|
-
* `repoRoot` is absent. Every existing engine test exercises this module via
|
|
361
|
-
* `opts.learnings` with no `repoRoot`, precisely to avoid touching this repo's
|
|
362
|
-
* OWN `.orchestrator/runtime/reconcile-candidates.jsonl`; a cwd fallback here
|
|
363
|
-
* would silently read it. The disk-touching default is reserved for callers
|
|
364
|
-
* that always pass an explicit `repoRoot` (the `/reconcile` skill resolves it
|
|
365
|
-
* via `git rev-parse --show-toplevel`).
|
|
366
|
-
* @param {string|undefined} repoRoot
|
|
367
|
-
* @returns {{ records: import('./idempotency.mjs').ReconcileCandidate[] }}
|
|
368
|
-
*/
|
|
369
|
-
function defaultLoadCandidatesForDedupe(repoRoot) {
|
|
370
|
-
if (typeof repoRoot !== 'string' || repoRoot.length === 0) return { records: [] };
|
|
371
|
-
const { records } = realLoadCandidates({ repoRoot });
|
|
372
|
-
return { records };
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/** Frontmatter form emitted by renderer.mjs: `learning-key: <value>` (no backticks, no leading dash). */
|
|
376
|
-
const FRONTMATTER_LEARNING_KEY_RE = /^learning-key:\s*(.+)$/gm;
|
|
377
|
-
/** Provenance-body form emitted by renderer.mjs: `` - learning-key: `<value>` ``. */
|
|
378
|
-
const BODY_LEARNING_KEY_RE = /-\s*learning-key:\s*`([^`]+)`/g;
|
|
379
|
-
/** Provenance-body form emitted by renderer.mjs: `` - learning-id: `<value>` ``. */
|
|
380
|
-
const BODY_LEARNING_ID_RE = /-\s*learning-id:\s*`([^`]+)`/g;
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Scan `<repoRoot>/.claude/rules/*.md` for the provenance markers the
|
|
384
|
-
* renderer stamps on every machine-generated rule — the frontmatter
|
|
385
|
-
* `learning-key:` line and the body `## Provenance` block's `learning-key`/
|
|
386
|
-
* `learning-id` bullets (`renderer.mjs`) — and return the two identity sets a
|
|
387
|
-
* learning can already be materialized under. A learning whose derived
|
|
388
|
-
* `learning_key` OR raw `.id` appears in either set already has a rule file
|
|
389
|
-
* on disk: re-proposing it is the issue #484 defect (9 of 10 proposals in one
|
|
390
|
-
* run were learnings a `.claude/rules/` file already covered).
|
|
391
|
-
*
|
|
392
|
-
* **This scan is the AUTHORITATIVE half of the dedupe contract** (#1242). The
|
|
393
|
-
* `.claude/rules/*.md` files it reads are TRACKED, so they survive a fresh
|
|
394
|
-
* clone, a wiped working copy, and any loss of `.orchestrator/runtime/` (which
|
|
395
|
-
* is gitignored — `.gitignore:114`). The idempotency sidecar consulted beside
|
|
396
|
-
* it is a CACHE that can only SHORT-CIRCUIT this scan, never replace it: on a
|
|
397
|
-
* fresh clone the sidecar is empty and correctness rests entirely on the
|
|
398
|
-
* markers below. Measured 2026-09-07 on this repo: with the sidecar emptied,
|
|
399
|
-
* the run produced the identical 10 proposals and 30 "already materialized"
|
|
400
|
-
* rejections; with this scan disabled instead, 5 already-consolidated
|
|
401
|
-
* learnings were re-proposed.
|
|
402
|
-
*
|
|
403
|
-
* Both marker forms are load-bearing. Frontmatter `learning-key:` is a YAML
|
|
404
|
-
* SCALAR and can name exactly ONE learning, so a CONSOLIDATED rule file (one
|
|
405
|
-
* file absorbing N learnings) carries the remaining N-1 identities ONLY as
|
|
406
|
-
* `## Provenance` body bullets. Breaking {@link BODY_LEARNING_KEY_RE} would
|
|
407
|
-
* therefore silently re-propose most of a consolidated corpus while every
|
|
408
|
-
* single-learning file still deduped correctly — pinned by the "fresh clone,
|
|
409
|
-
* consolidated shape" test in `tests/lib/reconcile/engine.test.mjs`.
|
|
410
|
-
*
|
|
411
|
-
* `rule-loader.mjs` only EXCLUDES expired rules from injection; it never
|
|
412
|
-
* deletes a file, so an expired rule keeps deduping through these markers.
|
|
413
|
-
*
|
|
414
|
-
* Gated the same way as {@link defaultLoadCandidatesForDedupe}: an absent
|
|
415
|
-
* `repoRoot` yields empty sets rather than falling back to `process.cwd()`.
|
|
416
|
-
* Never throws — a missing `.claude/rules/` dir or an unreadable file
|
|
417
|
-
* degrades to "nothing materialized" for that source, never a crash.
|
|
418
|
-
* @param {string|undefined} repoRoot
|
|
419
|
-
* @returns {{ keys: Set<string>, ids: Set<string> }}
|
|
420
|
-
*/
|
|
421
|
-
function defaultReadMaterializedProvenance(repoRoot) {
|
|
422
|
-
const keys = new Set();
|
|
423
|
-
const ids = new Set();
|
|
424
|
-
if (typeof repoRoot !== 'string' || repoRoot.length === 0) return { keys, ids };
|
|
425
|
-
|
|
426
|
-
const rulesDir = join(repoRoot, '.claude', 'rules');
|
|
427
|
-
let entries;
|
|
428
|
-
try {
|
|
429
|
-
entries = readdirSync(rulesDir);
|
|
430
|
-
} catch {
|
|
431
|
-
return { keys, ids }; // no rules dir yet → nothing materialized
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
for (const entry of entries) {
|
|
435
|
-
if (!entry.endsWith('.md')) continue;
|
|
436
|
-
let content;
|
|
437
|
-
try {
|
|
438
|
-
content = readFileSync(join(rulesDir, entry), 'utf8');
|
|
439
|
-
} catch {
|
|
440
|
-
continue; // unreadable file — skip it, do not fail the whole scan
|
|
441
|
-
}
|
|
442
|
-
for (const m of content.matchAll(FRONTMATTER_LEARNING_KEY_RE)) {
|
|
443
|
-
const v = m[1].trim();
|
|
444
|
-
if (v) keys.add(v);
|
|
445
|
-
}
|
|
446
|
-
for (const m of content.matchAll(BODY_LEARNING_KEY_RE)) {
|
|
447
|
-
const v = m[1].trim();
|
|
448
|
-
if (v) keys.add(v);
|
|
449
|
-
}
|
|
450
|
-
for (const m of content.matchAll(BODY_LEARNING_ID_RE)) {
|
|
451
|
-
const v = m[1].trim();
|
|
452
|
-
if (v && v !== 'n/a') ids.add(v);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
return { keys, ids };
|
|
456
|
-
}
|
|
457
|
-
|
|
458
315
|
/**
|
|
459
316
|
* Run the reconciliation engine.
|
|
460
317
|
*
|
|
@@ -597,32 +454,13 @@ async function runReconcileInner(
|
|
|
597
454
|
const { records: existingCandidates } = loadCandidatesForDedupe(repoRoot) ?? { records: [] };
|
|
598
455
|
const materialized = readMaterializedProvenance(repoRoot) ?? { keys: new Set(), ids: new Set() };
|
|
599
456
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const learningKey = rejectedLearningKey(learning);
|
|
606
|
-
const learningId =
|
|
607
|
-
learning &&
|
|
608
|
-
typeof learning === 'object' &&
|
|
609
|
-
typeof learning.id === 'string' &&
|
|
610
|
-
learning.id.length > 0
|
|
611
|
-
? learning.id
|
|
612
|
-
: null;
|
|
613
|
-
|
|
614
|
-
const sidecarTerminal =
|
|
615
|
-
learningKey !== null && isProcessed({ learning_key: learningKey }, existingCandidates);
|
|
616
|
-
const onDisk =
|
|
617
|
-
(learningKey !== null && materialized.keys.has(learningKey)) ||
|
|
618
|
-
(learningId !== null && materialized.ids.has(learningId));
|
|
619
|
-
|
|
620
|
-
if (!sidecarTerminal && !onDisk) {
|
|
621
|
-
stillEligible.push(learning);
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
457
|
+
const { stillEligible, materializedItems } = partitionMaterialized(eligible, {
|
|
458
|
+
existingCandidates,
|
|
459
|
+
materialized,
|
|
460
|
+
});
|
|
461
|
+
const alreadyMaterialized = materializedItems.length;
|
|
624
462
|
|
|
625
|
-
|
|
463
|
+
for (const { learning, learningKey, sidecarTerminal, onDisk } of materializedItems) {
|
|
626
464
|
const type = learningType(learning);
|
|
627
465
|
const reason = sidecarTerminal
|
|
628
466
|
? 'already processed — the idempotency sidecar already carries a terminal verdict for this learning-key'
|
|
@@ -882,6 +720,35 @@ export function assertKnownTrigger(trigger) {
|
|
|
882
720
|
/** Clamp for the `reason` string on the abort path — a message can be long. */
|
|
883
721
|
const REASON_MAX_CHARS = 300;
|
|
884
722
|
|
|
723
|
+
/**
|
|
724
|
+
* Replace the repo root and the home directory inside a free-text error message
|
|
725
|
+
* with the stable placeholders `<repo>` and `~`.
|
|
726
|
+
*
|
|
727
|
+
* An `fs` error message embeds the absolute path it failed on, so the abort
|
|
728
|
+
* `reason` otherwise carried `/Users/<name>/…` into the ledger and the optional
|
|
729
|
+
* webhook — sibling payloads never carry the absolute root (`docs/events-schema.md`).
|
|
730
|
+
* Runs BEFORE the clamp, so the 300 chars are spent on the tail, not on a prefix
|
|
731
|
+
* cut mid-path. The repo root goes first because it usually sits under home.
|
|
732
|
+
*
|
|
733
|
+
* Named ceiling (BV-004): only the two literal spellings are replaced. A path the
|
|
734
|
+
* OS reports in a different spelling — a symlink-resolved `/private/var/…` for a
|
|
735
|
+
* `/var/…` repo root — keeps its non-home prefix. REVISIT TRIGGER: a ledger
|
|
736
|
+
* `reason` carrying an absolute path outside both placeholders.
|
|
737
|
+
*
|
|
738
|
+
* @param {string} message
|
|
739
|
+
* @param {string|undefined} repoRoot
|
|
740
|
+
* @returns {string}
|
|
741
|
+
*/
|
|
742
|
+
function redactLocalPaths(message, repoRoot) {
|
|
743
|
+
let out = message;
|
|
744
|
+
// A relative or bare-`/` root would match arbitrary substrings — absolute only.
|
|
745
|
+
const root = typeof repoRoot === 'string' ? repoRoot.trim().replace(/\/+$/, '') : '';
|
|
746
|
+
if (root !== '' && isAbsolute(root)) out = out.split(root).join('<repo>');
|
|
747
|
+
const home = homedir().replace(/\/+$/, '');
|
|
748
|
+
if (home !== '' && isAbsolute(home)) out = out.split(home).join('~');
|
|
749
|
+
return out;
|
|
750
|
+
}
|
|
751
|
+
|
|
885
752
|
/** The closed target enum `resolveEffectiveTargets` recognises. */
|
|
886
753
|
const KNOWN_TARGETS = ['repo-local', 'baseline'];
|
|
887
754
|
|
|
@@ -904,7 +771,7 @@ const KNOWN_TARGETS = ['repo-local', 'baseline'];
|
|
|
904
771
|
* same boolean until 2027-03-13 (#1315).
|
|
905
772
|
*
|
|
906
773
|
* @param {ReconcileResult} result
|
|
907
|
-
* @param {{ trigger?: string, targets?: string[], dryRun?: boolean, durationMs: number }} ctx
|
|
774
|
+
* @param {{ repoRoot?: string, trigger?: string, targets?: string[], dryRun?: boolean, durationMs: number }} ctx
|
|
908
775
|
* @returns {Record<string, unknown>}
|
|
909
776
|
*/
|
|
910
777
|
function buildReconcilePayload(result, ctx) {
|
|
@@ -945,7 +812,7 @@ function buildReconcilePayload(result, ctx) {
|
|
|
945
812
|
if (typeof summary.skipped === 'number') payload.store_records_dropped = summary.skipped;
|
|
946
813
|
if (typeof result?.error === 'string' && result.error !== '') {
|
|
947
814
|
payload.aborted = 'engine-error';
|
|
948
|
-
payload.reason = result.error.slice(0, REASON_MAX_CHARS);
|
|
815
|
+
payload.reason = redactLocalPaths(result.error, ctx.repoRoot).slice(0, REASON_MAX_CHARS);
|
|
949
816
|
}
|
|
950
817
|
return payload;
|
|
951
818
|
}
|