session-orchestrator 3.17.0 → 3.20.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 +185 -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/commands/session.md +6 -2
- package/docs/USER-GUIDE.md +9 -4
- 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 +794 -0
- package/docs/rule-authoring.md +58 -9
- package/docs/session-config-reference.md +245 -50
- package/docs/session-config-template.md +39 -26
- package/hooks/_lib/guard-source-loader.mjs +680 -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 +226 -19
- package/hooks/enforce-scope.mjs +133 -9
- 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 +30 -4
- 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 +616 -164
- package/hooks/pre-bash-issue-budget.mjs +167 -0
- package/hooks/pre-bash-sessions-ledger-guard.mjs +1054 -0
- package/hooks/pre-bash-templates-first.mjs +96 -63
- package/hooks/subagent-telemetry.mjs +527 -37
- package/package.json +6 -3
- 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/backfill-learnings-from-vault.mjs +967 -0
- package/scripts/check-doc-consistency.sh +17 -1
- package/scripts/emit-session.mjs +3 -40
- 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 +1273 -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/hardening.mjs +9 -9
- 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/affinity.mjs +434 -0
- package/scripts/lib/learnings/candidates.mjs +736 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +408 -53
- package/scripts/lib/learnings/judgment.mjs +782 -0
- package/scripts/lib/learnings/kebab.mjs +128 -0
- package/scripts/lib/learnings/schema.mjs +43 -3
- package/scripts/lib/learnings/select.mjs +550 -0
- 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 +128 -24
- package/scripts/lib/reconcile/engine.mjs +156 -54
- package/scripts/lib/reconcile/idempotency.mjs +114 -14
- package/scripts/lib/reconcile/renderer.mjs +141 -25
- package/scripts/lib/reconcile/sanitize.mjs +518 -0
- package/scripts/lib/reconcile/writer.mjs +95 -1
- 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 +568 -145
- package/scripts/lib/session-close-backfill.mjs +63 -8
- 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-record-repair.mjs +551 -0
- package/scripts/lib/session-schema/constants.mjs +22 -3
- package/scripts/lib/session-schema/serializer.mjs +54 -0
- package/scripts/lib/session-schema/validator.mjs +16 -0
- package/scripts/lib/session-schema.mjs +1 -0
- package/scripts/lib/session-token-rollup.mjs +68 -6
- 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/soul-resolve.mjs +12 -0
- 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/tmux-layout/telemetry.mjs +43 -10
- package/scripts/lib/validate/check-agents.mjs +56 -0
- package/scripts/lib/validate/check-banner-parity.mjs +376 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +1148 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +244 -10
- package/scripts/lib/validate/check-learning-provenance.mjs +511 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +3 -3
- package/scripts/lib/validate/check-rules.mjs +244 -36
- 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/check-unwired-features.mjs +549 -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 +218 -16
- package/scripts/print-learnings-index.mjs +474 -0
- package/scripts/release.mjs +534 -0
- package/scripts/repair-invalid-sessions.mjs +209 -0
- package/scripts/run-quality-gate.mjs +123 -5
- package/scripts/sweep-expired-learnings.mjs +192 -32
- package/scripts/validate-plugin.mjs +21 -0
- 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/brainstorm/soul.md +47 -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 +117 -18
- package/skills/gitlab-ops/SKILL.md +25 -12
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/grill/soul.md +44 -1
- 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/plan/soul.md +46 -3
- package/skills/quality-gates/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +21 -4
- package/skills/session-end/SKILL.md +34 -36
- 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 +32 -2
- package/skills/session-end/plan-verification.md +6 -7
- package/skills/session-end/session-metrics-write.md +2 -0
- 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 +75 -7
- package/skills/session-start/phase-4-5-resource-health.md +15 -2
- package/skills/session-start/soul.md +41 -1
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/vault-sync/validator.mjs +108 -7
- package/skills/wave-executor/SKILL.md +6 -7
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/wave-loop.md +198 -80
- package/templates/_shared/loop.md +4 -4
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* learnings/kebab.mjs — the slug primitive behind every `learning_key`, and
|
|
3
|
+
* {@link learningKeyOf}, the whole-key derivation built on it.
|
|
4
|
+
*
|
|
5
|
+
* `learning_key` is not a stored field. It is DERIVED as
|
|
6
|
+
* `` `${type}/${kebab(title || subject)}` `` and is the logical identity the
|
|
7
|
+
* whole dedupe/idempotency layer keys on (`reconcile/idempotency.mjs` calls it
|
|
8
|
+
* "THE logical dedupe key"). That makes this a contract, not a formatting
|
|
9
|
+
* helper: two callers that derive differently do not produce two ugly slugs,
|
|
10
|
+
* they FORK the key space — the same learning renders under two identities,
|
|
11
|
+
* dedupe stops firing, and both halves look correct in isolation.
|
|
12
|
+
*
|
|
13
|
+
* Before this module existed there were four copies of `kebab` plus one inline
|
|
14
|
+
* expression, none exported, none shared (two of the four landed on the same
|
|
15
|
+
* day, from two different authors, each of whom flagged the duplication in
|
|
16
|
+
* their own report). They agreed on every reachable input — verified, not
|
|
17
|
+
* assumed — so consolidating here changed no existing key. See the byte-identity
|
|
18
|
+
* proof pinned in `tests/scripts/lib/learnings/kebab.test.mjs`.
|
|
19
|
+
*
|
|
20
|
+
* Consolidating the primitive left the SECOND half of the problem open, and it
|
|
21
|
+
* is the half that bites: what each call site FED the primitive. Four sites
|
|
22
|
+
* derived `${type}/…` verbatim while the writer (`reconcile/emitter.mjs`) alone
|
|
23
|
+
* derived `${kebab(type)}/…` — a divergence invisible while every live `type`
|
|
24
|
+
* is kebab-identical, and silent in BOTH directions the moment one is not.
|
|
25
|
+
* {@link learningKeyOf} exists so there is one derivation to agree with rather
|
|
26
|
+
* than five to keep in sync.
|
|
27
|
+
*
|
|
28
|
+
* ## Why this file lives under `learnings/` and not under `reconcile/`
|
|
29
|
+
*
|
|
30
|
+
* Dependency direction, measured rather than presumed (2026-08-13, HEAD
|
|
31
|
+
* 5d59e62): `grep -rn "from '../learnings/" scripts/lib/reconcile/` reports 6
|
|
32
|
+
* import edges across 4 files (`eligibility.mjs`, `engine.mjs` ×2,
|
|
33
|
+
* `emitter.mjs` ×2, `renderer.mjs` — three of them pulling THIS module, three
|
|
34
|
+
* pulling `learnings/schema.mjs`), and the reverse grep
|
|
35
|
+
* `from '../reconcile/'` over `scripts/lib/learnings/` reports 0. A primitive
|
|
36
|
+
* shared by both packages therefore belongs on the `learnings/` side; placing
|
|
37
|
+
* it under `reconcile/` would invert an edge and introduce a cycle.
|
|
38
|
+
*
|
|
39
|
+
* Deliberately NOT re-exported from the `scripts/lib/learnings.mjs` barrel —
|
|
40
|
+
* `surface.mjs` and `affinity.mjs` set that precedent: consumers import the
|
|
41
|
+
* leaf directly, so the barrel stays the historical schema/io/filters surface.
|
|
42
|
+
*
|
|
43
|
+
* ## What this module is NOT
|
|
44
|
+
*
|
|
45
|
+
* Not a general-purpose slugifier. Vault note ids, tag segments, and rule
|
|
46
|
+
* filenames have their own slug rules with their own length caps and charset
|
|
47
|
+
* contracts (`vault-mirror/utils.mjs`, `vault-archive.mjs`). Do not route those
|
|
48
|
+
* through here — a shared slugifier across unrelated identity spaces is how a
|
|
49
|
+
* cap added for one consumer silently re-keys another.
|
|
50
|
+
*
|
|
51
|
+
* Pure, stdlib-only, no imports, no clock, no fs.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Slugify a string into the stable kebab-case token used for learning keys.
|
|
56
|
+
*
|
|
57
|
+
* Lowercases, collapses every run of non-`[a-z0-9]` characters into a single
|
|
58
|
+
* `-`, and trims leading/trailing `-`.
|
|
59
|
+
*
|
|
60
|
+
* Three properties callers depend on:
|
|
61
|
+
*
|
|
62
|
+
* 1. **Total** — never throws. Non-string input is coerced via `String()`,
|
|
63
|
+
* so `null`/`undefined`/numbers yield `"null"`/`"undefined"`/`"12345"`
|
|
64
|
+
* rather than a `TypeError`. Callers pass values typed `unknown` at
|
|
65
|
+
* their trust boundary; a throw there would abort a reconcile run.
|
|
66
|
+
* 2. **Lossy on non-ASCII** — German umlauts and em-dashes in the corpus are
|
|
67
|
+
* collapsed to `-` (`"Größe"` → `"gr-e"`), NOT transliterated. Ugly, and
|
|
68
|
+
* deliberately frozen: every stamped key in `.claude/rules/*.md` and in
|
|
69
|
+
* `.orchestrator/runtime/reconcile-candidates.jsonl` was minted this way.
|
|
70
|
+
* Adding transliteration would re-key the entire corpus.
|
|
71
|
+
* 3. **May return the empty string** — an all-symbol input (`"!!!"`) yields
|
|
72
|
+
* `""`. `reconcile/renderer.mjs::deriveSlug` branches on exactly that to
|
|
73
|
+
* fall back to its hash suffix, so an "always return something non-empty"
|
|
74
|
+
* change here would silently disable that branch.
|
|
75
|
+
*
|
|
76
|
+
* @param {unknown} s Value to slugify; coerced with `String()`.
|
|
77
|
+
* @returns {string} Kebab token; `''` when the input holds no `[a-z0-9]`.
|
|
78
|
+
*/
|
|
79
|
+
export function kebab(s) {
|
|
80
|
+
return String(s)
|
|
81
|
+
.toLowerCase()
|
|
82
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
83
|
+
.replace(/^-+|-+$/g, '');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* THE derivation of a learning's logical key: `` `${type}/${kebab(title || subject)}` ``.
|
|
88
|
+
*
|
|
89
|
+
* One rule, four decisions, each of which was a divergence between call sites
|
|
90
|
+
* before this function existed:
|
|
91
|
+
*
|
|
92
|
+
* 1. **The TYPE half is verbatim (trimmed), never kebab'd.** The subject half
|
|
93
|
+
* is prose and must be slugged; the type half is an enum token a reader
|
|
94
|
+
* parses BACK OUT of the key — `backfill-learnings-from-vault.mjs` uses
|
|
95
|
+
* `learning_key.split('/')[0]` as the reconstructed `type` and
|
|
96
|
+
* cross-checks it against the vault note's type. Kebabbing it would make
|
|
97
|
+
* that half lossy, which is precisely the fidelity downgrade the backfill
|
|
98
|
+
* labels `derived:learning-key-slug (original prose not recoverable)` for
|
|
99
|
+
* the subject half. Frontmatter safety does not need it either: the key
|
|
100
|
+
* becomes an unquoted `learning-key:` scalar, and `reconcile/renderer.mjs`
|
|
101
|
+
* already asserts `LEARNING_KEY_RE` (`/^[a-z0-9/-]+$/`) on the whole value
|
|
102
|
+
* before rendering — an unsafe type is REJECTED loudly there rather than
|
|
103
|
+
* silently re-keyed here.
|
|
104
|
+
* 2. **`title` wins over `subject`**, and a whitespace-only `title` falls
|
|
105
|
+
* through to `subject` rather than yielding an empty slug.
|
|
106
|
+
* 3. **`null`, not a throw, for an unkeyable record.** Readers scan corpora
|
|
107
|
+
* that contain shape-foreign lines; an unkeyable record simply does not
|
|
108
|
+
* participate in key resolution. Writers that need a string check for
|
|
109
|
+
* `null` and reject the record with their own auditable reason.
|
|
110
|
+
* 4. **An empty slug is unkeyable, not a key.** `kebab('!!!')` is `''`, and
|
|
111
|
+
* `` `anti-pattern/` `` is not an identity — it is a bucket every
|
|
112
|
+
* all-symbol-subject record of that type would collide in.
|
|
113
|
+
*
|
|
114
|
+
* @param {unknown} record A learning record (or anything; non-records yield `null`).
|
|
115
|
+
* @returns {string|null} `` `${type}/${slug}` ``, or `null` when unkeyable.
|
|
116
|
+
*/
|
|
117
|
+
export function learningKeyOf(record) {
|
|
118
|
+
if (record === null || typeof record !== 'object' || Array.isArray(record)) return null;
|
|
119
|
+
const rec = /** @type {Record<string, unknown>} */ (record);
|
|
120
|
+
const type = typeof rec.type === 'string' ? rec.type.trim() : '';
|
|
121
|
+
const titleOrSubject =
|
|
122
|
+
(typeof rec.title === 'string' && rec.title.trim() !== '' ? rec.title : '') ||
|
|
123
|
+
(typeof rec.subject === 'string' && rec.subject.trim() !== '' ? rec.subject : '');
|
|
124
|
+
if (type === '' || titleOrSubject === '') return null;
|
|
125
|
+
const slug = kebab(titleOrSubject);
|
|
126
|
+
if (slug === '') return null;
|
|
127
|
+
return `${type}/${slug}`;
|
|
128
|
+
}
|
|
@@ -83,8 +83,13 @@ export const LEARNING_TYPE_REGISTRY = Object.freeze({
|
|
|
83
83
|
'fragile-file': Object.freeze({ ttlDays: 45, agentProposable: true, ruleConvertible: true }),
|
|
84
84
|
'effective-sizing': Object.freeze({ ttlDays: 45, agentProposable: true, ruleConvertible: false }),
|
|
85
85
|
'recurring-issue': Object.freeze({ ttlDays: 45, agentProposable: true, ruleConvertible: true }),
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
// workflow-pattern / proven-pattern: flipped ruleConvertible false->true
|
|
87
|
+
// (issue #900) — the real corpus census showed a large volume of live
|
|
88
|
+
// `workflow-pattern`/`proven-pattern` records (post type-alias-normalization,
|
|
89
|
+
// see LEARNING_TYPE_ALIASES below) that carried usable file_paths but were
|
|
90
|
+
// structurally unconvertible before this flip.
|
|
91
|
+
'workflow-pattern': Object.freeze({ ttlDays: 90, agentProposable: true, ruleConvertible: true }),
|
|
92
|
+
'proven-pattern': Object.freeze({ ttlDays: 90, agentProposable: true, ruleConvertible: true }),
|
|
88
93
|
'anti-pattern': Object.freeze({ ttlDays: 90, agentProposable: true, ruleConvertible: true }),
|
|
89
94
|
'autopilot-effectiveness': Object.freeze({ ttlDays: 90, agentProposable: true, ruleConvertible: false }),
|
|
90
95
|
// autonomy-verdict (#683): repo/scope readiness synthesis from autopilot
|
|
@@ -106,6 +111,30 @@ export const LEARNING_TYPE_REGISTRY = Object.freeze({
|
|
|
106
111
|
'stagnation-class-frequency': Object.freeze({ ttlDays: 60, agentProposable: false, ruleConvertible: true }),
|
|
107
112
|
});
|
|
108
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Free-form producer-dialect type names -> their canonical
|
|
116
|
+
* {@link LEARNING_TYPE_REGISTRY} counterpart (issue #900). The real corpus
|
|
117
|
+
* accumulated learnings stamped with type names that were never registered
|
|
118
|
+
* (`gotcha`, `pattern`, ...) — these are semantically the SAME classes as
|
|
119
|
+
* two registered types, just written with a different literal. Applied by
|
|
120
|
+
* {@link normalizeDialects} on both the read funnel (`normalizeLearning`)
|
|
121
|
+
* and the write/migration funnel (`migrateLegacyLearning`), so every
|
|
122
|
+
* downstream consumer (TTL derivation, `CONVERT_TYPES` / `PROPOSAL_TYPES`
|
|
123
|
+
* membership, reconcile eligibility) sees one canonical type space —
|
|
124
|
+
* mirrors the existing `files` -> `file_paths` dialect-normalization
|
|
125
|
+
* pattern one level up (type name instead of field name).
|
|
126
|
+
*
|
|
127
|
+
* INVARIANT (guarded by a test): no alias KEY may collide with a
|
|
128
|
+
* `LEARNING_TYPE_REGISTRY` key — a collision would silently rewrite an
|
|
129
|
+
* already-canonical type into a different one.
|
|
130
|
+
*
|
|
131
|
+
* @type {Readonly<Record<string, string>>}
|
|
132
|
+
*/
|
|
133
|
+
export const LEARNING_TYPE_ALIASES = Object.freeze({
|
|
134
|
+
gotcha: 'anti-pattern',
|
|
135
|
+
pattern: 'proven-pattern',
|
|
136
|
+
});
|
|
137
|
+
|
|
109
138
|
/**
|
|
110
139
|
* Per-type TTL policy (in days) for `expires_at` derivation. Derived from
|
|
111
140
|
* {@link LEARNING_TYPE_REGISTRY} — see parent module documentation for tier
|
|
@@ -275,7 +304,10 @@ const _warnedSessionIdConflict = new Set();
|
|
|
275
304
|
* session_id/source_session conflict (see below); idempotent; never throws;
|
|
276
305
|
* non-objects (and arrays) pass through unchanged. Does NOT mutate its input.
|
|
277
306
|
*
|
|
278
|
-
* Dialect rules (Epic #723 B2 — census 2026-07-02
|
|
307
|
+
* Dialect rules (Epic #723 B2 — census 2026-07-02; type-alias rule added
|
|
308
|
+
* issue #900):
|
|
309
|
+
* - `type` → canonical type — resolved via {@link LEARNING_TYPE_ALIASES}
|
|
310
|
+
* (e.g. `gotcha` → `anti-pattern`, `pattern` → `proven-pattern`).
|
|
279
311
|
* - `files` → `file_paths` — verbatim value move; an empty array is
|
|
280
312
|
* preserved as an empty `file_paths`; a canonical
|
|
281
313
|
* `file_paths` already present wins (legacy `files` dropped).
|
|
@@ -310,6 +342,14 @@ export function normalizeDialects(entry, { reserializeTimestamps = true } = {})
|
|
|
310
342
|
|
|
311
343
|
const out = { ...entry };
|
|
312
344
|
|
|
345
|
+
// type alias resolution (#900) — canonicalize free-form producer type names
|
|
346
|
+
// (e.g. `gotcha`, `pattern`) to their LEARNING_TYPE_REGISTRY counterpart so
|
|
347
|
+
// every downstream consumer (TTL derivation, CONVERT_TYPES / PROPOSAL_TYPES
|
|
348
|
+
// membership, reconcile eligibility) sees one canonical type space.
|
|
349
|
+
if (typeof out.type === 'string' && out.type in LEARNING_TYPE_ALIASES) {
|
|
350
|
+
out.type = LEARNING_TYPE_ALIASES[out.type];
|
|
351
|
+
}
|
|
352
|
+
|
|
313
353
|
// files → file_paths (verbatim value move; canonical wins; empty array kept).
|
|
314
354
|
if ('files' in out) {
|
|
315
355
|
if (!('file_paths' in out)) {
|