@event4u/agent-config 14.22.0 → 14.23.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +159 -380
- package/README.md +1 -1
- package/dist/agent-src/commands/pr/merge.md +47 -38
- package/dist/agent-src/commands/roadmap/next.md +8 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +12 -0
- package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
- package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -1
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +52 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +52 -4
- package/dist/cli-delegate/{chunk-TGIPZBCW.js → chunk-2KKXJW25.js} +22 -8
- package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-IAI3XL43.js} +15 -3
- package/dist/cli-delegate/{chunk-BIXEDQPZ.js → chunk-KCNTUSPI.js} +1 -1
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +20 -12
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_session_recycle.js +3 -13
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +8 -8
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +880 -289
- package/dist/install/install.mjs +15 -3
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +5458 -2453
- package/dist/scripts/_lib/lean_projection_mode.js +108 -0
- package/dist/scripts/_lib/router_match.js +243 -0
- package/dist/scripts/_lib/rule_injection.js +216 -0
- package/dist/scripts/condense.js +20 -20
- package/dist/scripts/project_thin_rules.js +139 -19
- package/dist/server/schemas/settings.js +16 -3
- package/docs/MIGRATION.md +56 -0
- package/docs/contracts/settings-classes.md +39 -7
- package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
- package/src/config/agent-settings.template.yml +47 -1
- package/src/config/continuity-surface.json +7 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-violation-baselines.json +2 -2
- package/src/config/hook-token-budget.json +7 -6
- package/src/config/packed-binary-manifest.json +1 -1
- package/src/config/routing-coverage-seed.json +9 -2
- package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
- package/src/scripts/_cli/handoff_sessions.ts +14 -44
- package/src/scripts/_lib/continuity_slot.ts +295 -0
- package/src/scripts/_lib/continuity_writer.ts +253 -0
- package/src/scripts/_lib/hook_settings.ts +85 -0
- package/src/scripts/_lib/lean_projection_mode.ts +127 -0
- package/src/scripts/_lib/packed_binary_predicate.ts +37 -0
- package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
- package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
- package/src/scripts/_lib/session_eol.ts +44 -0
- package/src/scripts/_lib/session_index_trust.ts +346 -0
- package/src/scripts/_lib/standing_bound_ratchet.ts +154 -0
- package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
- package/src/scripts/audit_mcp_tools.ts +69 -25
- package/src/scripts/build_mcp_catalog.ts +24 -1
- package/src/scripts/check_host_tree_parity.ts +291 -0
- package/src/scripts/check_preamble_payload_budget.ts +136 -7
- package/src/scripts/check_rule_projection_integrity.ts +102 -2
- package/src/scripts/ci_settle.ts +205 -5
- package/src/scripts/code_graph/build.ts +79 -0
- package/src/scripts/code_graph/cli.ts +258 -7
- package/src/scripts/code_graph/detect.ts +38 -0
- package/src/scripts/code_graph/query.ts +25 -2
- package/src/scripts/code_graph/sqlite_store.ts +16 -1
- package/src/scripts/code_graph/types.ts +52 -3
- package/src/scripts/code_graph/validate.ts +3 -1
- package/src/scripts/code_graph/verbs.ts +590 -0
- package/src/scripts/condense.ts +20 -20
- package/src/scripts/handoff_context_hook.ts +39 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +24 -1
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
- package/src/scripts/hooks/code_graph_context_hook.ts +7 -18
- package/src/scripts/hooks/push_settle_hook.ts +3 -2
- package/src/scripts/hooks/rule_inject_hook.ts +107 -20
- package/src/scripts/hooks/session_eol_hook.ts +120 -2
- package/src/scripts/hot_context_hook.ts +62 -7
- package/src/scripts/lint_roadmap_blockers.ts +164 -1
- package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
- package/src/scripts/mcp_server/graph_tools.ts +294 -0
- package/src/scripts/mcp_server/path_util.ts +31 -0
- package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
- package/src/scripts/mcp_server/tools.ts +3 -13
- package/src/scripts/model_rule_injection.ts +61 -4
- package/src/scripts/project_thin_rules.ts +149 -16
- package/src/scripts/report_standing_payload_by_host.ts +650 -0
- package/src/scripts/schemas/agent-settings.schema.json +7 -1
- package/src/scripts/session_memory_index.ts +81 -8
- package/src/server/schemas/settings.ts +23 -4
- package/src/vendor/grammars/README.md +27 -2
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vendored grammars' one anchor outside the manifest.
|
|
3
|
+
*
|
|
4
|
+
* WHAT THIS ANSWERS.
|
|
5
|
+
*
|
|
6
|
+
* `packed_binary_predicate.ts` verifies eleven conditions over
|
|
7
|
+
* `src/config/packed-binary-manifest.json`, and every one of them is
|
|
8
|
+
* manifest-internal: the manifest states a sha256 and the bytes are hashed
|
|
9
|
+
* against it, so a single commit that edits both passes all eleven. The
|
|
10
|
+
* `release/14.22.0` self-review finding `d1696732ac28` names exactly that gap.
|
|
11
|
+
* This module closes it by comparing the vendored bytes against a source the
|
|
12
|
+
* repository does not author: the `tree-sitter-wasms` release pinned in
|
|
13
|
+
* `package-lock.json` and installed by `npm ci`.
|
|
14
|
+
*
|
|
15
|
+
* WHAT IT MAY CLAIM, AND WHAT IT MAY NOT.
|
|
16
|
+
*
|
|
17
|
+
* It MAY claim: the vendored bytes equal the bytes of the locked upstream
|
|
18
|
+
* package, independent of the binary manifest's own self-description.
|
|
19
|
+
*
|
|
20
|
+
* It MUST NOT claim to be independent of all repository state, and it MUST NOT
|
|
21
|
+
* claim to verify the grammars cryptographically against the registry
|
|
22
|
+
* independent of local build state. It does neither. The chain is
|
|
23
|
+
* registry tarball -> `npm ci` checks its sha512 against the lock's
|
|
24
|
+
* `integrity` -> extracted bytes -> vendored bytes, and only the last link is
|
|
25
|
+
* checked here. An attacker who edits `node_modules` after install defeats
|
|
26
|
+
* this, and a lock entry that points at a hostile package defeats it earlier
|
|
27
|
+
* still — that link is review's job, not a check's, because the lock is
|
|
28
|
+
* human-readable text and the manifest is machine-only JSON.
|
|
29
|
+
*
|
|
30
|
+
* Both scoping sentences are the AI council's wording (2026-09-09, 2 seats,
|
|
31
|
+
* unanimous on this question), kept verbatim so a later reader can tell the
|
|
32
|
+
* boundary from an oversight.
|
|
33
|
+
*
|
|
34
|
+
* WHY IT REFUSES RATHER THAN SKIPS.
|
|
35
|
+
*
|
|
36
|
+
* An upstream comparison is trivially written so that it passes when the
|
|
37
|
+
* upstream is absent, which is a green that verifies nothing — the exact shape
|
|
38
|
+
* of the eleven conditions it is here to supplement. So an absent package, a
|
|
39
|
+
* version that is not the locked one, an absent counterpart file, an empty
|
|
40
|
+
* claim set, and two admitted entries sharing a filename are all refusals,
|
|
41
|
+
* never passes.
|
|
42
|
+
*
|
|
43
|
+
* The last of those is the subtlest, and it was added on an R2 review finding
|
|
44
|
+
* rather than foreseen: the upstream keeps its grammars in one flat directory,
|
|
45
|
+
* so the counterpart is addressed by basename. Two manifest rows with the same
|
|
46
|
+
* filename in different directories would anchor to the same upstream file —
|
|
47
|
+
* one admitted binary silently unanchored while the verdict reported full
|
|
48
|
+
* coverage, which is the false green this module exists to remove. The vendored
|
|
49
|
+
* side resolves the entry's OWN repo-relative path, never a basename under a
|
|
50
|
+
* fixed directory, for the same reason.
|
|
51
|
+
*/
|
|
52
|
+
import { createHash } from 'node:crypto';
|
|
53
|
+
import * as fs from 'node:fs';
|
|
54
|
+
import * as path from 'node:path';
|
|
55
|
+
|
|
56
|
+
import { loadManifest, VERIFIABLE_KIND, type PackedBinaryManifest } from './packed_binary_predicate.js';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The upstream this repository copies from. Hardcoded rather than derived: the
|
|
60
|
+
* vendored set exists precisely because the package is NOT a runtime
|
|
61
|
+
* dependency, so there is no dependency edge to read it off, and
|
|
62
|
+
* `src/vendor/grammars/README.md` names the same string as the refresh source.
|
|
63
|
+
*/
|
|
64
|
+
export const UPSTREAM_PACKAGE = 'tree-sitter-wasms';
|
|
65
|
+
|
|
66
|
+
/** Where the upstream keeps its compiled grammars inside its own package. */
|
|
67
|
+
const UPSTREAM_GRAMMAR_SUBDIR = 'out';
|
|
68
|
+
|
|
69
|
+
/** A refusal — the anchor could not be established, so no verdict is honest. */
|
|
70
|
+
export class UpstreamAnchorRefusal extends Error {
|
|
71
|
+
constructor(message: string) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.name = 'UpstreamAnchorRefusal';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** One vendored file compared against its upstream counterpart. */
|
|
78
|
+
export interface GrammarComparison {
|
|
79
|
+
readonly vendoredPath: string;
|
|
80
|
+
readonly upstreamPath: string;
|
|
81
|
+
readonly vendoredSha256: string;
|
|
82
|
+
readonly upstreamSha256: string;
|
|
83
|
+
readonly equal: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface UpstreamAnchorVerdict {
|
|
87
|
+
readonly lockedVersion: string;
|
|
88
|
+
readonly lockedIntegrity: string;
|
|
89
|
+
readonly installedVersion: string;
|
|
90
|
+
readonly comparisons: readonly GrammarComparison[];
|
|
91
|
+
/** Human-readable divergence lines. Empty iff every comparison is equal. */
|
|
92
|
+
readonly divergences: readonly string[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface UpstreamAnchorOptions {
|
|
96
|
+
readonly repoRoot: string;
|
|
97
|
+
/** Defaults to `<repoRoot>/node_modules/<UPSTREAM_PACKAGE>`. Fixture seam. */
|
|
98
|
+
readonly upstreamPackageDir?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Root the manifest's repo-relative `path` values resolve against.
|
|
101
|
+
* Defaults to `repoRoot`. Fixture seam — NOT a grammar directory: resolving
|
|
102
|
+
* by directory would discard the entry's own path, and two rows sharing a
|
|
103
|
+
* basename in different directories would then anchor to the same file.
|
|
104
|
+
*/
|
|
105
|
+
readonly vendoredRoot?: string;
|
|
106
|
+
/** Defaults to the manifest at `<repoRoot>/src/config/…`. Fixture seam. */
|
|
107
|
+
readonly manifest?: PackedBinaryManifest;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface LockPin {
|
|
111
|
+
readonly version: string;
|
|
112
|
+
readonly integrity: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The locked pin for the upstream, read from `package-lock.json`.
|
|
117
|
+
*
|
|
118
|
+
* `integrity` is not used to verify anything here — it is carried into the
|
|
119
|
+
* verdict so the caller can print the anchor it is trusting, and its absence
|
|
120
|
+
* is a refusal because a lock row without one pins a version and not bytes.
|
|
121
|
+
*/
|
|
122
|
+
export function readLockPin(repoRoot: string): LockPin {
|
|
123
|
+
const lockPath = path.join(repoRoot, 'package-lock.json');
|
|
124
|
+
if (!fs.existsSync(lockPath)) {
|
|
125
|
+
throw new UpstreamAnchorRefusal(`no package-lock.json at ${lockPath} — there is no locked upstream to anchor to`);
|
|
126
|
+
}
|
|
127
|
+
const lock = JSON.parse(fs.readFileSync(lockPath, 'utf-8')) as {
|
|
128
|
+
packages?: Record<string, { version?: string; integrity?: string }>;
|
|
129
|
+
};
|
|
130
|
+
const key = `node_modules/${UPSTREAM_PACKAGE}`;
|
|
131
|
+
const row = lock.packages?.[key];
|
|
132
|
+
if (row === undefined) {
|
|
133
|
+
throw new UpstreamAnchorRefusal(`package-lock.json carries no \`${key}\` row — the vendored grammars have no locked source`);
|
|
134
|
+
}
|
|
135
|
+
if (typeof row.version !== 'string' || row.version.length === 0) {
|
|
136
|
+
throw new UpstreamAnchorRefusal(`\`${key}\` in package-lock.json carries no version`);
|
|
137
|
+
}
|
|
138
|
+
if (typeof row.integrity !== 'string' || row.integrity.length === 0) {
|
|
139
|
+
throw new UpstreamAnchorRefusal(
|
|
140
|
+
`\`${key}\` in package-lock.json carries no \`integrity\` — the row pins a version but not bytes, so \`npm ci\` verifies nothing for it`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return { version: row.version, integrity: row.integrity };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The manifest, read through the predicate's own reader.
|
|
148
|
+
*
|
|
149
|
+
* A second reader would drift: a shape condition added there — a
|
|
150
|
+
* `schema_version` check, a kind allowlist — would leave this module accepting
|
|
151
|
+
* a manifest the predicate rejects, and this module's whole value is that it
|
|
152
|
+
* covers what the manifest admits. So `loadManifest` is the reader and this
|
|
153
|
+
* wrapper only re-types its failures as refusals, because an unreadable
|
|
154
|
+
* manifest means the anchor was not established rather than that it failed.
|
|
155
|
+
* R2 finding 2, 2026-09-09 — the duplicate reader this replaces was one
|
|
156
|
+
* identifier away from the import already at the top of this file.
|
|
157
|
+
*/
|
|
158
|
+
function readManifest(repoRoot: string): PackedBinaryManifest {
|
|
159
|
+
try {
|
|
160
|
+
return loadManifest(repoRoot);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
throw new UpstreamAnchorRefusal(
|
|
163
|
+
`the packed-binary manifest could not be read, so nothing was compared: ${err instanceof Error ? err.message : String(err)}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function sha256(file: string): string {
|
|
169
|
+
return createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Compare every vendored grammar the manifest admits against the locked
|
|
174
|
+
* upstream's copy of the same filename.
|
|
175
|
+
*
|
|
176
|
+
* Throws `UpstreamAnchorRefusal` when the anchor cannot be established at all;
|
|
177
|
+
* returns a verdict whose `divergences` is non-empty when it can be established
|
|
178
|
+
* and the bytes differ. The two are deliberately different outcomes: a refusal
|
|
179
|
+
* says "this was not checked", a divergence says "this was checked and is
|
|
180
|
+
* wrong", and collapsing them is how a check becomes a permanent green.
|
|
181
|
+
*/
|
|
182
|
+
export function verifyVendoredGrammarsAgainstUpstream(options: UpstreamAnchorOptions): UpstreamAnchorVerdict {
|
|
183
|
+
const { repoRoot } = options;
|
|
184
|
+
const upstreamDir = options.upstreamPackageDir ?? path.join(repoRoot, 'node_modules', UPSTREAM_PACKAGE);
|
|
185
|
+
const vendoredRoot = options.vendoredRoot ?? repoRoot;
|
|
186
|
+
const manifest = options.manifest ?? readManifest(repoRoot);
|
|
187
|
+
|
|
188
|
+
const pin = readLockPin(repoRoot);
|
|
189
|
+
|
|
190
|
+
const claims = manifest.entries.filter((e) => e.kind === VERIFIABLE_KIND);
|
|
191
|
+
if (claims.length === 0) {
|
|
192
|
+
throw new UpstreamAnchorRefusal(
|
|
193
|
+
`the manifest admits no \`${VERIFIABLE_KIND}\` entry — nothing was compared, and reporting that as a pass would be a green over an empty corpus`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// The upstream keeps its grammars in one flat directory, so the counterpart
|
|
198
|
+
// is addressed by basename. Two admitted entries sharing a basename would
|
|
199
|
+
// therefore point at the same upstream file, which is an ambiguous anchor
|
|
200
|
+
// rather than a comparison — refuse instead of picking one.
|
|
201
|
+
const byBase = new Map<string, string[]>();
|
|
202
|
+
for (const e of claims) {
|
|
203
|
+
const base = path.basename(e.path);
|
|
204
|
+
byBase.set(base, [...(byBase.get(base) ?? []), e.path]);
|
|
205
|
+
}
|
|
206
|
+
const ambiguous = [...byBase.entries()].filter(([, paths]) => paths.length > 1);
|
|
207
|
+
if (ambiguous.length > 0) {
|
|
208
|
+
throw new UpstreamAnchorRefusal(
|
|
209
|
+
`two or more admitted entries share a filename, so they would anchor to the same upstream file: ${ambiguous
|
|
210
|
+
.map(([base, paths]) => `${base} <- ${paths.join(', ')}`)
|
|
211
|
+
.join('; ')}`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const upstreamPkgJson = path.join(upstreamDir, 'package.json');
|
|
216
|
+
if (!fs.existsSync(upstreamPkgJson)) {
|
|
217
|
+
throw new UpstreamAnchorRefusal(
|
|
218
|
+
`${UPSTREAM_PACKAGE} is not installed at ${upstreamDir} — run \`npm ci\`. The vendored bytes were NOT verified; an absent upstream is a refusal, never a pass`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const installed = JSON.parse(fs.readFileSync(upstreamPkgJson, 'utf-8')) as { version?: string };
|
|
222
|
+
if (installed.version !== pin.version) {
|
|
223
|
+
throw new UpstreamAnchorRefusal(
|
|
224
|
+
`installed ${UPSTREAM_PACKAGE} is ${String(installed.version)} but package-lock.json pins ${pin.version} — comparing against an unlocked tree would anchor to nothing`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const comparisons: GrammarComparison[] = [];
|
|
229
|
+
const divergences: string[] = [];
|
|
230
|
+
|
|
231
|
+
for (const entry of claims) {
|
|
232
|
+
const vendoredPath = path.join(vendoredRoot, entry.path);
|
|
233
|
+
const upstreamPath = path.join(upstreamDir, UPSTREAM_GRAMMAR_SUBDIR, path.basename(entry.path));
|
|
234
|
+
if (!fs.existsSync(vendoredPath)) {
|
|
235
|
+
throw new UpstreamAnchorRefusal(`the manifest admits ${entry.path} but ${vendoredPath} does not exist`);
|
|
236
|
+
}
|
|
237
|
+
if (!fs.existsSync(upstreamPath)) {
|
|
238
|
+
throw new UpstreamAnchorRefusal(
|
|
239
|
+
`${entry.path} has no counterpart at ${upstreamPath} — the locked upstream does not carry this grammar, so this file has no anchor`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
const vendoredSha256 = sha256(vendoredPath);
|
|
243
|
+
const upstreamSha256 = sha256(upstreamPath);
|
|
244
|
+
const equal = vendoredSha256 === upstreamSha256;
|
|
245
|
+
comparisons.push({ vendoredPath, upstreamPath, vendoredSha256, upstreamSha256, equal });
|
|
246
|
+
if (!equal) {
|
|
247
|
+
divergences.push(
|
|
248
|
+
`${entry.path}: vendored sha256 ${vendoredSha256} != upstream ${upstreamSha256} (${UPSTREAM_PACKAGE}@${pin.version})`,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return buildVerdict({ pin, installedVersion: installed.version, comparisons, divergences });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Assemble the verdict from the two version sources, kept separate.
|
|
258
|
+
*
|
|
259
|
+
* Extracted so the sourcing is testable at all. On any tree the check accepts,
|
|
260
|
+
* `pin.version` and the installed version are equal — the strict guard above
|
|
261
|
+
* refuses otherwise — so a test driving the whole function can never tell which
|
|
262
|
+
* of the two the `installedVersion` field came from, and the round-1 regression
|
|
263
|
+
* test for exactly that defect passed with the defect restored. R2 finding 1,
|
|
264
|
+
* 2026-09-09. Here the two can be given different values, which is the only
|
|
265
|
+
* place the distinction is observable.
|
|
266
|
+
*/
|
|
267
|
+
export function buildVerdict(args: {
|
|
268
|
+
readonly pin: LockPin;
|
|
269
|
+
readonly installedVersion: string;
|
|
270
|
+
readonly comparisons: readonly GrammarComparison[];
|
|
271
|
+
readonly divergences: readonly string[];
|
|
272
|
+
}): UpstreamAnchorVerdict {
|
|
273
|
+
return {
|
|
274
|
+
lockedVersion: args.pin.version,
|
|
275
|
+
lockedIntegrity: args.pin.integrity,
|
|
276
|
+
installedVersion: args.installedVersion,
|
|
277
|
+
comparisons: args.comparisons,
|
|
278
|
+
divergences: args.divergences,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
@@ -29,6 +29,30 @@ const _HERE = fileURLToPath(import.meta.url);
|
|
|
29
29
|
export const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
30
30
|
export const CATALOG = path.join(ROOT, 'src/scripts/mcp_server/consumer_tool_catalog.json');
|
|
31
31
|
export const TOOLS_TS = path.join(ROOT, 'src/scripts/mcp_server/tools.ts');
|
|
32
|
+
/**
|
|
33
|
+
* Modules that contribute records to `ALLOWLIST` from OUTSIDE `tools.ts`.
|
|
34
|
+
*
|
|
35
|
+
* `tools.ts` sits past the source-size ceiling, so a tool family whose records
|
|
36
|
+
* would cost ~250 units of excess there is defined in its own module and spread
|
|
37
|
+
* into the allowlist. This generator indexed `tools.ts` alone, so the five
|
|
38
|
+
* `graph_*` tools rendered `_stub-only_` in the published inventory — the doc
|
|
39
|
+
* asserting the exact opposite of the catalogue's own `implemented_on: ["stdio"]`,
|
|
40
|
+
* and `--check` staying green because it compared the doc against its own wrong
|
|
41
|
+
* render. Found by an independent review.
|
|
42
|
+
*
|
|
43
|
+
* A LIST rather than a directory walk: a walk would silently pick up any future
|
|
44
|
+
* file with a matching shape, and the point of this index is to say where a
|
|
45
|
+
* handler actually is. Adding a family here is one line in the same change that
|
|
46
|
+
* adds the family.
|
|
47
|
+
*/
|
|
48
|
+
export const HANDLER_MODULES: readonly { file: string; label: string; block: string }[] = [
|
|
49
|
+
{ file: path.join(ROOT, 'src/scripts/mcp_server/tools.ts'), label: 'tools.ts', block: 'export const ALLOWLIST' },
|
|
50
|
+
{
|
|
51
|
+
file: path.join(ROOT, 'src/scripts/mcp_server/graph_tools.ts'),
|
|
52
|
+
label: 'graph_tools.ts',
|
|
53
|
+
block: 'export const GRAPH_TOOLS',
|
|
54
|
+
},
|
|
55
|
+
];
|
|
32
56
|
export const OUT = path.join(ROOT, 'docs/contracts/mcp-tool-inventory.md');
|
|
33
57
|
|
|
34
58
|
// Match `<name>: {` entries at 4-space indent inside the TS ALLOWLIST record
|
|
@@ -71,28 +95,36 @@ function _relativeToRoot(p: string): string {
|
|
|
71
95
|
return path.relative(ROOT, p).split(path.sep).join('/');
|
|
72
96
|
}
|
|
73
97
|
|
|
74
|
-
function _index_handlers(): Map<string, number> {
|
|
75
|
-
const out = new Map<string, number>();
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const line = lines[idx] as string;
|
|
82
|
-
if (line.startsWith('export const ALLOWLIST')) {
|
|
83
|
-
inAllowlist = true;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (inAllowlist && line.startsWith('};')) {
|
|
87
|
-
inAllowlist = false;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (!inAllowlist) {
|
|
98
|
+
function _index_handlers(): Map<string, { label: string; line: number }> {
|
|
99
|
+
const out = new Map<string, { label: string; line: number }>();
|
|
100
|
+
for (const mod of HANDLER_MODULES) {
|
|
101
|
+
let text: string;
|
|
102
|
+
try {
|
|
103
|
+
text = fs.readFileSync(mod.file, 'utf-8');
|
|
104
|
+
} catch {
|
|
91
105
|
continue;
|
|
92
106
|
}
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
const lines = _splitlines(text);
|
|
108
|
+
// Only index entries inside the module's own registration block.
|
|
109
|
+
let inBlock = false;
|
|
110
|
+
for (let idx = 0; idx < lines.length; idx += 1) {
|
|
111
|
+
const i = idx + 1;
|
|
112
|
+
const line = lines[idx] as string;
|
|
113
|
+
if (line.startsWith(mod.block)) {
|
|
114
|
+
inBlock = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (inBlock && line.startsWith('};')) {
|
|
118
|
+
inBlock = false;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!inBlock) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const m = HANDLER_RE.exec(line);
|
|
125
|
+
if (m) {
|
|
126
|
+
out.set(m[1] as string, { label: mod.label, line: i });
|
|
127
|
+
}
|
|
96
128
|
}
|
|
97
129
|
}
|
|
98
130
|
return out;
|
|
@@ -117,7 +149,11 @@ function _joinSorted(obj: Map<string, number>): string {
|
|
|
117
149
|
return keys.map((k) => `${k}=${obj.get(k)}`).join(', ');
|
|
118
150
|
}
|
|
119
151
|
|
|
120
|
-
function _render(
|
|
152
|
+
function _render(
|
|
153
|
+
catalog: Catalog,
|
|
154
|
+
handlers: Map<string, { label: string; line: number }>,
|
|
155
|
+
cat_lines: Map<string, number>,
|
|
156
|
+
): string {
|
|
121
157
|
const tools = catalog.tools;
|
|
122
158
|
const total = tools.length;
|
|
123
159
|
const by_transport = new Map<string, number>();
|
|
@@ -145,7 +181,14 @@ function _render(catalog: Catalog, handlers: Map<string, number>, cat_lines: Map
|
|
|
145
181
|
lines.push('');
|
|
146
182
|
lines.push('# MCP tool inventory');
|
|
147
183
|
lines.push('');
|
|
148
|
-
|
|
184
|
+
// A BARE command name, and deliberately not a link to this file: an
|
|
185
|
+
// independent review flagged the previous `../../audit_mcp_tools` as a
|
|
186
|
+
// path that resolves nowhere and proposed `src/scripts/audit_mcp_tools.ts`.
|
|
187
|
+
// That fix is refused by `generated_by.test.ts` § 1.4, which forbids a
|
|
188
|
+
// generated artifact from hard-coding its generator's script path — the
|
|
189
|
+
// broken link and the literal path are two wrong answers, and the
|
|
190
|
+
// replacement form the rule names is the runnable command.
|
|
191
|
+
lines.push('> Generated by `audit_mcp_tools`. Do not edit.');
|
|
149
192
|
lines.push('> from the source-of-truth catalog');
|
|
150
193
|
lines.push('> [`src/scripts/mcp_server/consumer_tool_catalog.json`](../../src/scripts/mcp_server/consumer_tool_catalog.json).');
|
|
151
194
|
lines.push('> Do **not** hand-edit; rerun `./scripts-run src/scripts/audit_mcp_tools --write`.');
|
|
@@ -172,10 +215,10 @@ function _render(catalog: Catalog, handlers: Map<string, number>, cat_lines: Map
|
|
|
172
215
|
cat_line !== undefined
|
|
173
216
|
? `[\`consumer_tool_catalog.json:${cat_line}\`](../../src/scripts/mcp_server/consumer_tool_catalog.json#L${cat_line})`
|
|
174
217
|
: '_missing_';
|
|
175
|
-
const
|
|
218
|
+
const h = handlers.get(name);
|
|
176
219
|
const h_cite =
|
|
177
|
-
|
|
178
|
-
? `[
|
|
220
|
+
h !== undefined
|
|
221
|
+
? `[\`${h.label}:${h.line}\`](../../src/scripts/mcp_server/${h.label}#L${h.line})`
|
|
179
222
|
: '_stub-only_';
|
|
180
223
|
lines.push(`| \`${name}\` | \`${side}\` | ${transports} | ${cat_cite} | ${h_cite} |`);
|
|
181
224
|
}
|
|
@@ -240,6 +283,7 @@ export function main(argv: string[] | null = null): number {
|
|
|
240
283
|
candidates: [
|
|
241
284
|
path.posix.join('src', 'scripts', 'mcp_server', 'consumer_tool_catalog.json'),
|
|
242
285
|
path.posix.join('src', 'scripts', 'mcp_server', 'tools.ts'),
|
|
286
|
+
path.posix.join('src', 'scripts', 'mcp_server', 'graph_tools.ts'),
|
|
243
287
|
],
|
|
244
288
|
repoRoot: ROOT,
|
|
245
289
|
});
|
|
@@ -163,7 +163,30 @@ export function _buildCatalog(): Record<string, unknown> {
|
|
|
163
163
|
return {
|
|
164
164
|
schema_version: 1,
|
|
165
165
|
description: CATALOG_DESCRIPTION,
|
|
166
|
-
|
|
166
|
+
// `agent-config mcp-server`, not an `npx` form — AI council 2026-09-08,
|
|
167
|
+
// 2/2 convergent (road-to-a-graph-that-is-shipped 4.2, Fork 3 option 5).
|
|
168
|
+
//
|
|
169
|
+
// Both npx shapes are unusable here and for different reasons.
|
|
170
|
+
// `npx -y <pkg>@<version> mcp-server` is what `docs/mcp-server.md:97`
|
|
171
|
+
// documents and what the pin rationale at `docs/mcp-server.md:82` argues
|
|
172
|
+
// for, but the step's own verify requires the literal `npx -y` to be
|
|
173
|
+
// ABSENT from this file. Dropping the `-y` to satisfy that makes npx
|
|
174
|
+
// PROMPT before installing a package that is not present, which in a
|
|
175
|
+
// non-interactive MCP client start is a hang rather than a prompt.
|
|
176
|
+
//
|
|
177
|
+
// The installed binary is what the setup docs lead with
|
|
178
|
+
// (`docs/setup/mcp-client-config.md:35`,
|
|
179
|
+
// `docs/getting-started-local-stdio.md:9` — "the turnkey path … one
|
|
180
|
+
// command"), is non-interactive, and resolves no dist-tag at all
|
|
181
|
+
// because it IS whatever the consumer installed. Its cost is stated in
|
|
182
|
+
// the catalog description rather than hidden: it assumes the package is
|
|
183
|
+
// on PATH.
|
|
184
|
+
//
|
|
185
|
+
// RECORDED CONFLICT, per the same council verdict: this step's verify
|
|
186
|
+
// and the docs' pinned form contradict each other directly — the test
|
|
187
|
+
// forbids the string the documentation recommends. That is a defect in
|
|
188
|
+
// one of the two, and it is named here rather than silently normalised.
|
|
189
|
+
install_hint_stdio: `${_packageName().split('/').pop() ?? 'agent-config'} mcp-server`,
|
|
167
190
|
tools: entries,
|
|
168
191
|
};
|
|
169
192
|
}
|