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.
- package/.agents/docs/agentrc-reference.json +4 -0
- package/.agents/docs/configuration.md +3 -0
- package/.agents/rules/ci-remediation.md +39 -21
- package/.agents/schemas/agentrc.schema.json +19 -0
- package/.agents/scripts/audit-to-stories.js +222 -75
- package/.agents/scripts/file-ci-gap.js +306 -0
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +25 -1
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +40 -52
- package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +5 -1
- package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +162 -0
- package/.agents/scripts/lib/audit-to-stories/issues-file.js +121 -0
- package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/ledger-record.js +126 -0
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +11 -0
- package/.agents/scripts/lib/config-settings-schema.js +33 -0
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +53 -13
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +71 -25
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +18 -25
- package/.agents/scripts/lib/{audit-to-stories/ledger.js → findings/audit-ledger.js} +131 -24
- package/.agents/scripts/lib/findings/route-finding.js +38 -0
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/github/framework-repo.js +148 -2
- package/.agents/scripts/lib/label-constants.js +6 -1
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/orchestration/ci-gap-intake.js +605 -0
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +13 -8
- package/.agents/scripts/lib/orchestration/plan-persist/audit-provenance.js +197 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +15 -2
- package/.agents/scripts/lib/orchestration/run-epilogue.js +4 -4
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +32 -20
- package/.agents/scripts/pr-watch-with-update.js +3 -2
- package/.agents/workflows/audit-to-stories.md +63 -27
- package/.agents/workflows/helpers/deliver-story-reference.md +19 -4
- package/.agents/workflows/helpers/plan-reference.md +23 -0
- package/.agents/workflows/mandrel-plan.md +6 -6
- package/docs/CHANGELOG.md +10 -0
- package/package.json +1 -1
|
@@ -1,6 +1,152 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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)
|
|
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
|
/**
|