mandrel 2.55.0 → 2.56.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.
Files changed (37) hide show
  1. package/.agents/docs/agentrc-reference.json +4 -0
  2. package/.agents/docs/configuration.md +3 -0
  3. package/.agents/rules/ci-remediation.md +39 -21
  4. package/.agents/schemas/agentrc.schema.json +19 -0
  5. package/.agents/scripts/audit-to-stories.js +222 -75
  6. package/.agents/scripts/file-ci-gap.js +306 -0
  7. package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +25 -1
  8. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +40 -52
  9. package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +5 -1
  10. package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +162 -0
  11. package/.agents/scripts/lib/audit-to-stories/issues-file.js +121 -0
  12. package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +1 -1
  13. package/.agents/scripts/lib/audit-to-stories/ledger-record.js +126 -0
  14. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +11 -0
  15. package/.agents/scripts/lib/config-settings-schema.js +33 -0
  16. package/.agents/scripts/lib/feedback-loop/graduator-core.js +53 -13
  17. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +71 -25
  18. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +18 -25
  19. package/.agents/scripts/lib/{audit-to-stories/ledger.js → findings/audit-ledger.js} +131 -24
  20. package/.agents/scripts/lib/findings/route-finding.js +38 -0
  21. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  22. package/.agents/scripts/lib/github/framework-repo.js +148 -2
  23. package/.agents/scripts/lib/label-constants.js +6 -1
  24. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  25. package/.agents/scripts/lib/orchestration/ci-gap-intake.js +605 -0
  26. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +13 -8
  27. package/.agents/scripts/lib/orchestration/plan-persist/audit-provenance.js +197 -0
  28. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +15 -2
  29. package/.agents/scripts/lib/orchestration/run-epilogue.js +4 -4
  30. package/.agents/scripts/lib/orchestration/story-follow-ups.js +32 -20
  31. package/.agents/scripts/pr-watch-with-update.js +3 -2
  32. package/.agents/workflows/audit-to-stories.md +63 -27
  33. package/.agents/workflows/helpers/deliver-story-reference.md +19 -4
  34. package/.agents/workflows/helpers/plan-reference.md +23 -0
  35. package/.agents/workflows/mandrel-plan.md +6 -6
  36. package/docs/CHANGELOG.md +10 -0
  37. package/package.json +1 -1
@@ -1,6 +1,152 @@
1
1
  /**
2
- * Canonical framework repository slug used by follow-up / graduator
3
- * paths when the consumer config does not supply owner/repo.
2
+ * framework-repo.js — the follow-up **ownership routing** SSOT: which
3
+ * repository a finding, a retro proposal, or a CI-gap intake issue is filed
4
+ * in, and what to say when that question has no answer.
5
+ *
6
+ * ## Three buckets, not two
7
+ *
8
+ * A defect surfaced by one repository's CI is not necessarily that
9
+ * repository's to fix. Ownership splits three ways:
10
+ *
11
+ * - `consumer` — the repo the run is standing in (`github.owner`/`repo`).
12
+ * - `framework` — the Mandrel framework itself
13
+ * (`github.followUpRepos.framework`, defaulted to the mirror constant).
14
+ * - `platform` — a shared platform / infrastructure repo that neither of
15
+ * the other two owns: a shared base config, a runner fleet, a
16
+ * cross-repo toolchain (`github.followUpRepos.platform`, **no default**
17
+ * — nothing can guess a shared repo's slug).
18
+ *
19
+ * ## Why there is no `?? currentRepo` fallback
20
+ *
21
+ * The two-bucket predecessor resolved a framework-tagged item with
22
+ * `frameworkRepo ? frameworkRepo : currentRepo`. When the config key was
23
+ * absent that expression filed framework-owned work into the **consumer's**
24
+ * repo while the rendered retro claimed it went to the framework repo — a
25
+ * silent mis-file, recorded in `retro-proposals-graduator.js`'s own file-top
26
+ * comment. The failure was invisible in this repository precisely because
27
+ * consumer === framework here.
28
+ *
29
+ * So an unresolvable bucket is a first-class outcome, never a fallback:
30
+ * `routeOwnership` returns `routable: false` plus the `missingKey` that
31
+ * would fix it, and every caller must decide **out loud** what to do with
32
+ * that — file locally and say so in the body (the CI-gap filer), or defer
33
+ * and surface it where an operator will see it (the graduators). What no
34
+ * caller may do is route it somewhere plausible and stay quiet.
4
35
  */
5
36
 
37
+ /**
38
+ * Canonical framework repository slug, used when the consumer config does
39
+ * not supply `github.followUpRepos.framework`. The `framework` bucket is the
40
+ * one bucket with a knowable default: it is this framework.
41
+ */
6
42
  export const DEFAULT_FRAMEWORK_REPO = 'dsj1984/mandrel';
43
+
44
+ /** The closed ownership-bucket set. */
45
+ export const OWNERSHIP_BUCKETS = Object.freeze([
46
+ 'consumer',
47
+ 'framework',
48
+ 'platform',
49
+ ]);
50
+
51
+ /**
52
+ * The `.agentrc.json` key behind each bucket, quoted verbatim when a bucket
53
+ * is unroutable so the operator is told which key to set rather than that
54
+ * "routing failed".
55
+ */
56
+ const OWNERSHIP_CONFIG_KEYS = Object.freeze({
57
+ consumer: 'github.owner / github.repo',
58
+ framework: 'github.followUpRepos.framework',
59
+ platform: 'github.followUpRepos.platform',
60
+ });
61
+
62
+ /**
63
+ * Parse an `"<owner>/<repo>"` slug into `{ owner, repo }`, or `null` when the
64
+ * slug is absent, empty, or malformed. A `null` return is the signal an
65
+ * unroutable bucket is built from — never a reason to substitute another
66
+ * repo.
67
+ *
68
+ * @param {string|null|undefined} slug
69
+ * @returns {{ owner: string, repo: string } | null}
70
+ */
71
+ export function parseRepoSlug(slug) {
72
+ if (typeof slug !== 'string') return null;
73
+ const parts = slug.trim().split('/');
74
+ if (parts.length !== 2) return null;
75
+ const [owner, repo] = parts;
76
+ if (!owner || !repo) return null;
77
+ return { owner, repo };
78
+ }
79
+
80
+ /**
81
+ * Render a `{ owner, repo }` pair back to its slug, or `null` when the pair
82
+ * is absent/malformed.
83
+ *
84
+ * @param {{ owner?: string, repo?: string }|null|undefined} repo
85
+ * @returns {string|null}
86
+ */
87
+ export function formatRepoSlug(repo) {
88
+ if (!repo || typeof repo !== 'object') return null;
89
+ const { owner, repo: name } = /** @type {{owner?: string, repo?: string}} */ (
90
+ repo
91
+ );
92
+ if (typeof owner !== 'string' || typeof name !== 'string') return null;
93
+ if (!owner.trim() || !name.trim()) return null;
94
+ return `${owner.trim()}/${name.trim()}`;
95
+ }
96
+
97
+ /**
98
+ * Resolve every ownership bucket from a resolved `.agentrc` config.
99
+ *
100
+ * `framework` falls back to {@link DEFAULT_FRAMEWORK_REPO}; `platform` has no
101
+ * default and stays `null` when unconfigured; `consumer` is `null` when
102
+ * `github.owner`/`github.repo` are unset. A `null` bucket is an honest
103
+ * "unknown", which {@link routeOwnership} turns into a named, reportable
104
+ * outcome.
105
+ *
106
+ * @param {object} [config] — resolved `.agentrc` config.
107
+ * @returns {{ consumer: ({owner: string, repo: string}|null), framework: ({owner: string, repo: string}|null), platform: ({owner: string, repo: string}|null) }}
108
+ */
109
+ export function resolveOwnershipRepos(config) {
110
+ const github = config?.github ?? {};
111
+ const followUp = github?.followUpRepos ?? {};
112
+ const owner = typeof github.owner === 'string' ? github.owner.trim() : '';
113
+ const repo = typeof github.repo === 'string' ? github.repo.trim() : '';
114
+ return {
115
+ consumer: owner && repo ? { owner, repo } : null,
116
+ framework:
117
+ parseRepoSlug(followUp.framework) ??
118
+ parseRepoSlug(DEFAULT_FRAMEWORK_REPO),
119
+ platform: parseRepoSlug(followUp.platform),
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Route one ownership bucket to the repository its work belongs in.
125
+ *
126
+ * Total: an unknown bucket, an absent repos map, and an unconfigured bucket
127
+ * all resolve to `routable: false` with the `missingKey` that would fix it —
128
+ * never to a substituted repository.
129
+ *
130
+ * @param {object} opts
131
+ * @param {string} opts.bucket — one of {@link OWNERSHIP_BUCKETS}.
132
+ * @param {{consumer?: object|null, framework?: object|null, platform?: object|null}} opts.repos
133
+ * — resolved buckets, from {@link resolveOwnershipRepos} or assembled by a
134
+ * caller that already holds the repo objects.
135
+ * @param {{owner: string, repo: string}|null} [opts.currentRepo] — the repo
136
+ * the run is standing in, used only to report `crossRepo`.
137
+ * @returns {{ bucket: string, routedRepo: ({owner: string, repo: string}|null), routable: boolean, missingKey: (string|null), crossRepo: boolean }}
138
+ */
139
+ export function routeOwnership({ bucket, repos, currentRepo = null } = {}) {
140
+ const known = OWNERSHIP_BUCKETS.includes(bucket);
141
+ const routedRepo = known ? (repos?.[bucket] ?? null) : null;
142
+ const routable = Boolean(routedRepo?.owner && routedRepo?.repo);
143
+ const missingKey = routable
144
+ ? null
145
+ : (OWNERSHIP_CONFIG_KEYS[bucket] ?? `unknown ownership bucket "${bucket}"`);
146
+ const crossRepo =
147
+ routable &&
148
+ Boolean(currentRepo) &&
149
+ (routedRepo.owner !== currentRepo.owner ||
150
+ routedRepo.repo !== currentRepo.repo);
151
+ return { bucket, routedRepo, routable, missingKey, crossRepo };
152
+ }
@@ -119,7 +119,11 @@ export const ACCEPTANCE_NA = ACCEPTANCE_LABELS.N_A;
119
119
  * loop). `meta::framework-gap` is applied to issues that surface a defect or
120
120
  * missing capability in the framework itself; `meta::consumer-improvement`
121
121
  * is applied to issues that surface improvements to a consumer project
122
- * (workflow tweaks, ergonomic asks, doc polish). The `/mandrel-plan` Phase 0
122
+ * (workflow tweaks, ergonomic asks, doc polish); `meta::platform-gap` is
123
+ * applied to issues owned by neither — a shared base config, a runner fleet,
124
+ * a cross-repo toolchain. The three mirror the ownership buckets in
125
+ * `lib/github/framework-repo.js`, so a routed filing's label and its
126
+ * destination repository cannot disagree. The `/mandrel-plan` Phase 0
123
127
  * fetcher (see `lib/feedback-loop/prior-feedback-fetcher.js`) reads open
124
128
  * issues carrying either label and surfaces them to the planner so retro
125
129
  * signals are routed into durable substrates rather than lost in chat.
@@ -127,6 +131,7 @@ export const ACCEPTANCE_NA = ACCEPTANCE_LABELS.N_A;
127
131
  export const META_LABELS = {
128
132
  FRAMEWORK_GAP: 'meta::framework-gap',
129
133
  CONSUMER_IMPROVEMENT: 'meta::consumer-improvement',
134
+ PLATFORM_GAP: 'meta::platform-gap',
130
135
  };
131
136
 
132
137
  /**
@@ -115,6 +115,7 @@ const FRAMEWORK_SCRIPT_BASENAMES = Object.freeze([
115
115
  'diagnose.js',
116
116
  'drain-pending-cleanup.js',
117
117
  'evidence-gate.js',
118
+ 'file-ci-gap.js',
118
119
  'generate-config-docs.js',
119
120
  'generate-lens-checklists.js',
120
121
  'generate-skills-index.js',