@event4u/agent-config 14.19.0 → 14.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +108 -156
- package/dist/agent-src/commands/agent-handoff.md +42 -41
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +7 -1
- package/dist/agent-src/rules/untrusted-input-defense.md +0 -2
- package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +1 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +28 -6
- package/dist/agent-src/skills/test-driven-development/SKILL.md +1 -3
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +14 -1
- package/dist/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
- package/dist/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
- package/dist/agent-src/templates/scripts/telemetry_report.ts +22 -2
- package/dist/cli/mcp/dispatch.js +10 -0
- package/dist/cli/mcp/stdio.js +33 -2
- package/dist/cli/mcp/telemetry.js +227 -0
- package/dist/cli-delegate/{chunk-GZTMRYCN.js → chunk-C6IPZRDA.js} +79 -12
- package/dist/cli-delegate/{chunk-CYH5SP6M.js → chunk-I6RHFQEG.js} +151 -16
- package/dist/cli-delegate/{chunk-IDHUQIXP.js → chunk-NUMQHLS5.js} +21 -4
- package/dist/cli-delegate/{chunk-PMEKGDED.js → chunk-PI2GU3DP.js} +163 -1
- package/dist/cli-delegate/{chunk-7PRE6ZV2.js → chunk-YS6XTLEK.js} +199 -4
- package/dist/cli-delegate/chunk-ZVBOHSVF.js +1109 -0
- package/dist/cli-delegate/cmd_conformance.js +73 -109
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +41 -11
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +59 -902
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- 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 +9 -9
- 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 +7257 -6497
- package/dist/install/install.mjs +393 -168
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +497 -118
- package/dist/scripts/mcp_server/telemetry.js +148 -0
- package/docs/guidelines/agent-infra/naming.md +1 -1
- package/docs/guidelines/agent-infra/security-lint-containment.md +46 -6
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +4 -4
- package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +1 -1
- package/docs/guidelines/redundancy-taxonomy.md +28 -0
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
- package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
- package/src/agent-src/templates/scripts/telemetry_report.ts +22 -2
- package/src/config/assurance-capability-registry.json +9 -0
- package/src/config/gate-coverage.yml +55 -0
- package/src/config/gate-violation-baselines.json +15 -3
- package/src/scripts/_cli/cmd_session_recycle.ts +51 -3
- package/src/scripts/_lib/audit_field_provenance.ts +44 -3
- package/src/scripts/_lib/host_capability.ts +38 -0
- package/src/scripts/_lib/install_reach_checks.ts +33 -1
- package/src/scripts/_lib/mcp_bridge.ts +222 -10
- package/src/scripts/_lib/mcp_consent_residual.ts +173 -0
- package/src/scripts/_lib/orchestration_record.ts +23 -1
- package/src/scripts/_lib/recycle_envelope_paths.ts +190 -0
- package/src/scripts/_lib/release_position.ts +96 -0
- package/src/scripts/_lib/review_skipped_record.ts +17 -1
- package/src/scripts/_lib/security_lint.ts +179 -7
- package/src/scripts/_lib/self_repair.ts +141 -6
- package/src/scripts/_lib/self_repair_store.ts +273 -7
- package/src/scripts/_lib/subagent_capsule.ts +155 -3
- package/src/scripts/build_rule_trigger_matrix.ts +5 -0
- package/src/scripts/check_mcp_doc_drift.ts +217 -0
- package/src/scripts/check_pack_size.ts +321 -3
- package/src/scripts/handoff_context_hook.ts +54 -7
- package/src/scripts/hooks/session_eol_hook.ts +18 -7
- package/src/scripts/install.ts +4 -4
- package/src/scripts/lint_agent_security.ts +316 -25
- package/src/scripts/lint_confusables.ts +42 -6
- package/src/scripts/lint_handoffs.ts +0 -132
- package/src/scripts/lint_hidden_unicode.ts +49 -7
- package/src/scripts/lint_instruction_smuggling.ts +42 -6
- package/src/scripts/lint_mcp_config_security.ts +42 -6
- package/src/scripts/lint_roadmap_later_disposition.ts +217 -27
- package/src/scripts/lint_skill_frontmatter_safety.ts +43 -7
- package/src/scripts/mcp_server/resources.ts +41 -0
- package/src/scripts/mcp_server/telemetry.ts +19 -4
- package/src/scripts/orchestration_record.ts +12 -1
- package/src/scripts/release.ts +24 -24
- package/src/scripts/release_publication.ts +6 -2
- package/src/scripts/self_repair_cli.ts +31 -1
- package/src/scripts/skill_scout.ts +69 -0
- package/dist/cli-delegate/chunk-2GNWGMAL.js +0 -172
- package/dist/cli-delegate/chunk-LPPWO5CE.js +0 -161
|
@@ -23,10 +23,48 @@
|
|
|
23
23
|
* sitting in the active tree silently counts as backlog the dashboard and
|
|
24
24
|
* ``/roadmap:process-*`` would try to execute.
|
|
25
25
|
*
|
|
26
|
-
* B. Every roadmap
|
|
27
|
-
* condition** so it never rots without a "when does it come back"
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* B. Every parked roadmap MUST record a **wake
|
|
27
|
+
* condition** so it never rots without a "when does it come back".
|
|
28
|
+
*
|
|
29
|
+
* Until 2026-09-07 this rule short-circuited on the frontmatter STATUS
|
|
30
|
+
* word: ``if (status !== 'later' && !RESUME_RE.test(body))``, so a file
|
|
31
|
+
* whose frontmatter said ``status: later`` passed with no resume text
|
|
32
|
+
* present at all. Measured over the tree at that date, 63 of 82 parked
|
|
33
|
+
* roadmaps passed on the status word alone, 8 on a real ``Blocked until``
|
|
34
|
+
* / ``Resume when``, and 11 on the bare word ``trigger`` — so 74 of 82
|
|
35
|
+
* carried no machine-readable wake condition. The word ``trigger`` was
|
|
36
|
+
* the second hole: a body that merely mentions it in passing ("the
|
|
37
|
+
* trigger fires on push") satisfied a governance gate.
|
|
38
|
+
*
|
|
39
|
+
* The corrected contract, in force regardless of ``status``:
|
|
40
|
+
*
|
|
41
|
+
* 1. **Structured** — an ``entry_condition:`` MAPPING in the
|
|
42
|
+
* frontmatter with three non-empty parts:
|
|
43
|
+
*
|
|
44
|
+
* entry_condition:
|
|
45
|
+
* what: what would change the decision
|
|
46
|
+
* when: when that could arrive
|
|
47
|
+
* who: who would have to act (``none`` is a legal answer)
|
|
48
|
+
*
|
|
49
|
+
* A condition that cannot be observed is a deferral wearing a
|
|
50
|
+
* condition's clothes, which is why all three are required and why
|
|
51
|
+
* blankness is not the same answer as ``none``.
|
|
52
|
+
*
|
|
53
|
+
* 2. **Legacy** — a body line matching ``Blocked until`` /
|
|
54
|
+
* ``Resume when`` / ``blocked-until`` / ``resume-when``. Kept so the
|
|
55
|
+
* ratchet below has a floor to walk DOWN toward rather than a tree
|
|
56
|
+
* to fail on the day it lands.
|
|
57
|
+
*
|
|
58
|
+
* A malformed ``entry_condition`` (scalar, or missing a part) is a HARD
|
|
59
|
+
* finding and is never baselined: it is a defect the author introduced in
|
|
60
|
+
* the change that wrote it, not inherited debt.
|
|
61
|
+
*
|
|
62
|
+
* C. Every parked roadmap MUST carry ``review_by``
|
|
63
|
+
* in its frontmatter. Ratcheted from the measured count, same reasoning.
|
|
64
|
+
*
|
|
65
|
+
* Both ratchets live in ``src/config/gate-violation-baselines.json`` under
|
|
66
|
+
* ``lint_roadmap_later_disposition:wake-condition`` and
|
|
67
|
+
* ``…:review-by``: the count may fall and never rise.
|
|
30
68
|
*
|
|
31
69
|
* Rationale: "roadmaps with open tasks deferred for later are always moved to
|
|
32
70
|
* ``later/``" (user directive 2026-06-16). The active tree holds only roadmaps
|
|
@@ -44,6 +82,7 @@ import * as fs from 'node:fs';
|
|
|
44
82
|
import * as path from 'node:path';
|
|
45
83
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
46
84
|
|
|
85
|
+
import { checkRatchet } from './_lib/gate_baseline.js';
|
|
47
86
|
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
48
87
|
|
|
49
88
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -64,8 +103,17 @@ function _setRoadmapRootForTest(p: string): void {
|
|
|
64
103
|
const FRONTMATTER_RE = /^---[ \t\f\v]*\n([\s\S]*?)\n---[ \t\f\v]*\n/;
|
|
65
104
|
// STATUS_RE = re.compile(r"^status:\s*([A-Za-z0-9_-]+)\s*$", re.MULTILINE)
|
|
66
105
|
const STATUS_RE = /^status:[ \t\f\v\r\n]*([A-Za-z0-9_-]+)[ \t\f\v\r]*$/m;
|
|
67
|
-
//
|
|
68
|
-
|
|
106
|
+
// The four UNAMBIGUOUS phrases. `trigger` was dropped 2026-09-07: it matched
|
|
107
|
+
// any prose mention of the word, so 11 of 82 parked roadmaps satisfied a
|
|
108
|
+
// governance gate on a sentence about something else entirely.
|
|
109
|
+
const RESUME_RE = /\b(blocked until|resume when|blocked-until|resume-when)\b/i;
|
|
110
|
+
|
|
111
|
+
/** Ratchet keys in `src/config/gate-violation-baselines.json`. */
|
|
112
|
+
export const WAKE_GATE = 'lint_roadmap_later_disposition:wake-condition';
|
|
113
|
+
export const REVIEW_BY_GATE = 'lint_roadmap_later_disposition:review-by';
|
|
114
|
+
|
|
115
|
+
/** The three parts a structured `entry_condition` must name. */
|
|
116
|
+
export const ENTRY_CONDITION_PARTS = ['what', 'when', 'who'] as const;
|
|
69
117
|
|
|
70
118
|
// Non-roadmap files that live in the tree but are not roadmaps.
|
|
71
119
|
const EXCLUDE_NAMES: ReadonlySet<string> = new Set([
|
|
@@ -79,6 +127,67 @@ const EXCLUDE_PREFIXES: readonly string[] = ['open-questions'];
|
|
|
79
127
|
interface Violation {
|
|
80
128
|
file: string;
|
|
81
129
|
reason: string;
|
|
130
|
+
/**
|
|
131
|
+
* `hard` fails immediately; `wake` and `review-by` are ratcheted against a
|
|
132
|
+
* committed baseline. A hard finding is one the AUTHOR introduced (a
|
|
133
|
+
* malformed structured field, a misplaced `status: later`); a ratcheted one
|
|
134
|
+
* is debt the corrected gate revealed.
|
|
135
|
+
*/
|
|
136
|
+
cls: 'hard' | 'wake' | 'review-by';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The three-part reading of a structured `entry_condition`.
|
|
141
|
+
*
|
|
142
|
+
* Deliberately parsed from the raw frontmatter text rather than through a YAML
|
|
143
|
+
* loader: this gate's contract is byte-pinned against a retired Python
|
|
144
|
+
* implementation and must not acquire a parser dependency whose error
|
|
145
|
+
* behavior differs. The shape is fixed and shallow, so a line reader is
|
|
146
|
+
* sufficient and its failure mode is legible.
|
|
147
|
+
*/
|
|
148
|
+
export function entryConditionParts(frontmatter: string): {
|
|
149
|
+
present: boolean;
|
|
150
|
+
scalar: boolean;
|
|
151
|
+
parts: Record<string, string>;
|
|
152
|
+
} {
|
|
153
|
+
const lines = frontmatter.split('\n');
|
|
154
|
+
const idx = lines.findIndex((l) => /^entry_condition:/.test(l));
|
|
155
|
+
if (idx === -1) {
|
|
156
|
+
return { present: false, scalar: false, parts: {} };
|
|
157
|
+
}
|
|
158
|
+
const head = (lines[idx] as string).slice('entry_condition:'.length).trim();
|
|
159
|
+
if (head !== '') {
|
|
160
|
+
// `entry_condition: "…"` — the pre-contract scalar form.
|
|
161
|
+
return { present: true, scalar: true, parts: {} };
|
|
162
|
+
}
|
|
163
|
+
const parts: Record<string, string> = {};
|
|
164
|
+
for (let i = idx + 1; i < lines.length; i++) {
|
|
165
|
+
const line = lines[i] as string;
|
|
166
|
+
if (!/^\s+\S/.test(line)) break;
|
|
167
|
+
const m = /^\s+([a-z_]+):\s*(.*)$/.exec(line);
|
|
168
|
+
if (m === null) continue;
|
|
169
|
+
parts[m[1] as string] = _unquote((m[2] as string).trim());
|
|
170
|
+
}
|
|
171
|
+
return { present: true, scalar: false, parts };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function _unquote(v: string): string {
|
|
175
|
+
if (v.length >= 2 && ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))) {
|
|
176
|
+
return v.slice(1, -1).trim();
|
|
177
|
+
}
|
|
178
|
+
return v.trim();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Missing / empty part names, `[]` when the field satisfies the contract. */
|
|
182
|
+
export function entryConditionProblems(frontmatter: string): string[] {
|
|
183
|
+
const read = entryConditionParts(frontmatter);
|
|
184
|
+
if (!read.present) return ['absent'];
|
|
185
|
+
if (read.scalar) return ['scalar'];
|
|
186
|
+
const missing: string[] = [];
|
|
187
|
+
for (const part of ENTRY_CONDITION_PARTS) {
|
|
188
|
+
if ((read.parts[part] ?? '').trim() === '') missing.push(part);
|
|
189
|
+
}
|
|
190
|
+
return missing;
|
|
82
191
|
}
|
|
83
192
|
|
|
84
193
|
function _is_roadmap(p: string): boolean {
|
|
@@ -167,6 +276,7 @@ function check(root: string): Violation[] {
|
|
|
167
276
|
if (status === 'later' && !in_later) {
|
|
168
277
|
out.push({
|
|
169
278
|
file: rel,
|
|
279
|
+
cls: 'hard',
|
|
170
280
|
reason:
|
|
171
281
|
'frontmatter `status: later` but file is not under ' +
|
|
172
282
|
'`agents/roadmaps/later/` — a blocked-for-later roadmap ' +
|
|
@@ -175,19 +285,57 @@ function check(root: string): Violation[] {
|
|
|
175
285
|
});
|
|
176
286
|
}
|
|
177
287
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
288
|
+
if (!in_later) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const fm = _frontmatter(text);
|
|
293
|
+
const body = text.slice(fm.length);
|
|
294
|
+
const problems = entryConditionProblems(fm);
|
|
295
|
+
const structured = problems.length === 0;
|
|
296
|
+
|
|
297
|
+
// Rule B(1) — a PRESENT entry_condition must satisfy the contract.
|
|
298
|
+
// Hard, never ratcheted: a malformed field is authored, not inherited.
|
|
299
|
+
if (!structured && problems[0] !== 'absent') {
|
|
300
|
+
out.push({
|
|
301
|
+
file: rel,
|
|
302
|
+
cls: 'hard',
|
|
303
|
+
reason:
|
|
304
|
+
problems[0] === 'scalar'
|
|
305
|
+
? '`entry_condition` is a scalar string — it must be a mapping naming ' +
|
|
306
|
+
'`what` (what would change the decision), `when` (when it could ' +
|
|
307
|
+
'arrive) and `who` (who would have to act; `none` is a legal answer).'
|
|
308
|
+
: '`entry_condition` is missing or blank in: ' +
|
|
309
|
+
problems.join(', ') +
|
|
310
|
+
'. A condition that cannot be observed is a deferral wearing a ' +
|
|
311
|
+
"condition's clothes; `none` is a legal answer for `who` and " +
|
|
312
|
+
'blankness is not.',
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Rule B(2) — a wake condition is required REGARDLESS of `status`.
|
|
317
|
+
if (!structured && !RESUME_RE.test(body)) {
|
|
318
|
+
out.push({
|
|
319
|
+
file: rel,
|
|
320
|
+
cls: 'wake',
|
|
321
|
+
reason:
|
|
322
|
+
'roadmap under `agents/roadmaps/later/` has no wake condition — the ' +
|
|
323
|
+
'frontmatter `status` word no longer satisfies this gate, and neither ' +
|
|
324
|
+
'does the bare word `trigger`. Add a structured `entry_condition` ' +
|
|
325
|
+
'(`what` / `when` / `who`) or a `Blocked until` / `Resume when` line.',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Rule C — review_by in the frontmatter of every parked roadmap.
|
|
330
|
+
if (!/^review_by:\s*\S/m.test(fm)) {
|
|
331
|
+
out.push({
|
|
332
|
+
file: rel,
|
|
333
|
+
cls: 'review-by',
|
|
334
|
+
reason:
|
|
335
|
+
'roadmap under `agents/roadmaps/later/` has no `review_by:` in its ' +
|
|
336
|
+
'frontmatter — a park with no review date is indistinguishable from ' +
|
|
337
|
+
'an abandonment.',
|
|
338
|
+
});
|
|
191
339
|
}
|
|
192
340
|
}
|
|
193
341
|
return out;
|
|
@@ -337,24 +485,65 @@ function main(argv?: readonly string[]): number {
|
|
|
337
485
|
}
|
|
338
486
|
|
|
339
487
|
const violations = check(ROADMAP_ROOT);
|
|
488
|
+
const hard = violations.filter((v) => v.cls === 'hard');
|
|
489
|
+
const wake = violations.filter((v) => v.cls === 'wake');
|
|
490
|
+
const reviewBy = violations.filter((v) => v.cls === 'review-by');
|
|
340
491
|
|
|
341
492
|
if (args.json) {
|
|
342
|
-
//
|
|
343
|
-
|
|
493
|
+
// The `file` / `reason` pair is the pinned shape; `cls` is additive, so
|
|
494
|
+
// a consumer reading the two original keys is unaffected.
|
|
495
|
+
const payload = violations.map((v) => ({ file: v.file, reason: v.reason, cls: v.cls }));
|
|
344
496
|
process.stdout.write(_jsonDumpsIndent2(payload) + '\n');
|
|
345
|
-
|
|
497
|
+
// A JSON consumer wants the findings, not the ratchet verdict; the exit
|
|
498
|
+
// code still carries it.
|
|
499
|
+
return _verdict(hard, wake, reviewBy, true);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (hard.length) {
|
|
346
503
|
process.stdout.write('❌ later/ disposition violations:\n');
|
|
347
|
-
for (const v of
|
|
504
|
+
for (const v of hard) {
|
|
348
505
|
process.stdout.write(` • ${v.file}\n ${v.reason}\n`);
|
|
349
506
|
}
|
|
350
|
-
}
|
|
507
|
+
}
|
|
508
|
+
return _verdict(hard, wake, reviewBy, false);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Ratcheted verdict. The two revealed-debt classes print their findings either
|
|
513
|
+
* way — at baseline they are the backlog this gate is walking down, and
|
|
514
|
+
* printing them is what makes the next lowering possible.
|
|
515
|
+
*/
|
|
516
|
+
function _verdict(
|
|
517
|
+
hard: readonly Violation[],
|
|
518
|
+
wake: readonly Violation[],
|
|
519
|
+
reviewBy: readonly Violation[],
|
|
520
|
+
quiet: boolean,
|
|
521
|
+
): number {
|
|
522
|
+
let failed = hard.length > 0;
|
|
523
|
+
for (const [gate, found] of [
|
|
524
|
+
[WAKE_GATE, wake],
|
|
525
|
+
[REVIEW_BY_GATE, reviewBy],
|
|
526
|
+
] as const) {
|
|
527
|
+
const verdict = checkRatchet({ gate, actual: found.length, repoRoot: REPO_ROOT });
|
|
528
|
+
if (!verdict.ok) {
|
|
529
|
+
failed = true;
|
|
530
|
+
if (!quiet) {
|
|
531
|
+
for (const v of found) {
|
|
532
|
+
process.stderr.write(` • ${v.file}\n ${v.reason}\n`);
|
|
533
|
+
}
|
|
534
|
+
process.stderr.write(`❌ ${verdict.message}\n`);
|
|
535
|
+
}
|
|
536
|
+
} else if (!quiet) {
|
|
537
|
+
process.stdout.write(`✅ ${verdict.message}\n`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (!failed && !quiet) {
|
|
351
541
|
process.stdout.write(
|
|
352
542
|
'✅ later/ disposition: every blocked-for-later roadmap is parked ' +
|
|
353
|
-
'correctly
|
|
543
|
+
'correctly, and both wake-condition ratchets hold.\n',
|
|
354
544
|
);
|
|
355
545
|
}
|
|
356
|
-
|
|
357
|
-
return violations.length ? 1 : 0;
|
|
546
|
+
return failed ? 1 : 0;
|
|
358
547
|
}
|
|
359
548
|
|
|
360
549
|
function _isCliEntry(): boolean {
|
|
@@ -386,6 +575,7 @@ if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
|
386
575
|
export {
|
|
387
576
|
REPO_ROOT,
|
|
388
577
|
ROADMAP_ROOT,
|
|
578
|
+
type Violation,
|
|
389
579
|
LATER_DIR,
|
|
390
580
|
FRONTMATTER_RE,
|
|
391
581
|
STATUS_RE,
|
|
@@ -138,9 +138,19 @@ export function grantSurface(rel: string): GrantSurface {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
export function _scan(sf: sl.ScannedFile): sl.Finding[] {
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
// A BOUND pragma suppresses only the matches it fingerprints, so the scan
|
|
142
|
+
// runs and its output is filtered at the end. An UNBOUND one keeps the old
|
|
143
|
+
// whole-file bail and reports itself.
|
|
144
|
+
const pragmaForm = sf.pragma_form(CHECK);
|
|
145
|
+
if (pragmaForm === 'legacy') {
|
|
146
|
+
return [sl.legacy_pragma_finding(sf, CHECK)];
|
|
143
147
|
}
|
|
148
|
+
// EVERY exit from this function goes through `finish`. Wrapping only the
|
|
149
|
+
// terminal `return` is the defect that shipped for one commit here: this
|
|
150
|
+
// scan has an early return for an artifact with no `execution:` block, and a
|
|
151
|
+
// bound pragma silently did not apply to exactly those artifacts.
|
|
152
|
+
const finish = (hits: sl.Finding[]): sl.Finding[] =>
|
|
153
|
+
pragmaForm === 'bound' ? sf.filter_bound_pragma(CHECK, hits) : hits;
|
|
144
154
|
const fm = _frontmatter(sf);
|
|
145
155
|
if (!fm) {
|
|
146
156
|
return [];
|
|
@@ -261,7 +271,7 @@ export function _scan(sf: sl.ScannedFile): sl.Finding[] {
|
|
|
261
271
|
}
|
|
262
272
|
|
|
263
273
|
if (Object.keys(ex).length === 0) {
|
|
264
|
-
return out;
|
|
274
|
+
return finish(out);
|
|
265
275
|
}
|
|
266
276
|
const etype = _stripQuotes((ex['type'] ?? [0, ''])[1]);
|
|
267
277
|
const safety = _stripQuotes((ex['safety_mode'] ?? [0, ''])[1]);
|
|
@@ -331,15 +341,17 @@ export function _scan(sf: sl.ScannedFile): sl.Finding[] {
|
|
|
331
341
|
),
|
|
332
342
|
);
|
|
333
343
|
}
|
|
334
|
-
return out;
|
|
344
|
+
return finish(out);
|
|
335
345
|
}
|
|
336
346
|
|
|
337
347
|
interface Args {
|
|
348
|
+
/** Scan base for the bounded root mode; absent = the package root. */
|
|
349
|
+
root?: string;
|
|
338
350
|
json: boolean;
|
|
339
351
|
}
|
|
340
352
|
|
|
341
353
|
function _argError(msg: string): never {
|
|
342
|
-
process.stderr.write('usage: lint_skill_frontmatter_safety [-h] [--json]\n');
|
|
354
|
+
process.stderr.write('usage: lint_skill_frontmatter_safety [-h] [--json] [--root DIR]\n');
|
|
343
355
|
process.stderr.write(`lint_skill_frontmatter_safety: error: ${msg}\n`);
|
|
344
356
|
process.exit(2);
|
|
345
357
|
}
|
|
@@ -347,12 +359,28 @@ function _argError(msg: string): never {
|
|
|
347
359
|
function parse_args(argv: readonly string[]): Args {
|
|
348
360
|
const out: Args = { json: false };
|
|
349
361
|
const extra: string[] = [];
|
|
362
|
+
let rootPending = false;
|
|
350
363
|
for (const a of argv) {
|
|
364
|
+
if (rootPending) {
|
|
365
|
+
out.root = a;
|
|
366
|
+
rootPending = false;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
351
369
|
if (a === '-h' || a === '--help') {
|
|
352
|
-
process.stdout.write('usage: lint_skill_frontmatter_safety [-h] [--json]\n');
|
|
370
|
+
process.stdout.write('usage: lint_skill_frontmatter_safety [-h] [--json] [--root DIR]\n');
|
|
353
371
|
process.exit(0);
|
|
354
372
|
} else if (a === '--json') {
|
|
355
373
|
out.json = true;
|
|
374
|
+
} else if (a === '--root' || a.startsWith('--root=')) {
|
|
375
|
+
// Bounded root mode. Absent, this linter's own default roots stand;
|
|
376
|
+
// present, the same relative roots resolve against DIR instead. Used by
|
|
377
|
+
// the scout to scan a quarantined candidate, and by --self-test fixtures.
|
|
378
|
+
const eq = a.indexOf('=');
|
|
379
|
+
if (eq !== -1) {
|
|
380
|
+
out.root = a.slice(eq + 1);
|
|
381
|
+
} else {
|
|
382
|
+
rootPending = true;
|
|
383
|
+
}
|
|
356
384
|
} else {
|
|
357
385
|
extra.push(a);
|
|
358
386
|
}
|
|
@@ -365,6 +393,7 @@ function parse_args(argv: readonly string[]): Args {
|
|
|
365
393
|
|
|
366
394
|
export function main(argv: readonly string[] | null = null): number {
|
|
367
395
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
396
|
+
const scanBase = args.root === undefined ? sl.ROOT : path.resolve(args.root);
|
|
368
397
|
|
|
369
398
|
const findings: sl.Finding[] = [];
|
|
370
399
|
// `src/subagents` is the fourth root, added because its omission is what made
|
|
@@ -372,7 +401,7 @@ export function main(argv: readonly string[] | null = null): number {
|
|
|
372
401
|
// bare shell lives there, and the gate had never read it.
|
|
373
402
|
const roots = ['src/skills', 'src/agent-src', 'src/domains', 'src/subagents'];
|
|
374
403
|
let scanned = 0;
|
|
375
|
-
for (const sf of sl.iter_corpus(roots, ['.md'])) {
|
|
404
|
+
for (const sf of sl.iter_corpus(roots, ['.md'], scanBase)) {
|
|
376
405
|
scanned += 1;
|
|
377
406
|
for (const h of _scan(sf)) {
|
|
378
407
|
findings.push(h);
|
|
@@ -400,6 +429,13 @@ export function main(argv: readonly string[] | null = null): number {
|
|
|
400
429
|
throw exc;
|
|
401
430
|
}
|
|
402
431
|
|
|
432
|
+
// Publish what this child inspected so the umbrella can aggregate a real
|
|
433
|
+
// corpus size rather than a count of children. Same number the assertion
|
|
434
|
+
// above just accepted, so the published figure cannot drift from it.
|
|
435
|
+
if (args.json) {
|
|
436
|
+
sl.report_child_scanned(scanned);
|
|
437
|
+
}
|
|
438
|
+
|
|
403
439
|
if (args.json) {
|
|
404
440
|
const payload = sl.py_json_dumps_indent2(findings.map((f) => f.toDict()));
|
|
405
441
|
process.stdout.write(payload + '\n');
|
|
@@ -41,6 +41,10 @@ export interface Resource {
|
|
|
41
41
|
readonly source: string;
|
|
42
42
|
readonly mime_type: string;
|
|
43
43
|
readonly kind: ResourceKind;
|
|
44
|
+
/** Frontmatter `type:` (`always` / `auto` / `manual`) where the asset has one. */
|
|
45
|
+
readonly rule_type?: string;
|
|
46
|
+
/** File mtime, ISO-8601, for the protocol's `lastModified` annotation. */
|
|
47
|
+
readonly last_modified?: string;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
// Python: re.compile(r"^#\s+(.+?)\s*$", re.MULTILINE)
|
|
@@ -160,9 +164,20 @@ function _load(
|
|
|
160
164
|
source: meta.source ?? 'package',
|
|
161
165
|
mime_type: MIME_MARKDOWN,
|
|
162
166
|
kind,
|
|
167
|
+
...(meta.type === undefined ? {} : { rule_type: meta.type.trim() }),
|
|
168
|
+
...(_mtimeIso(p) === null ? {} : { last_modified: _mtimeIso(p) as string }),
|
|
163
169
|
};
|
|
164
170
|
}
|
|
165
171
|
|
|
172
|
+
/** File mtime as ISO-8601, or `null` when it cannot be read. */
|
|
173
|
+
function _mtimeIso(p: string): string | null {
|
|
174
|
+
try {
|
|
175
|
+
return fs.statSync(p).mtime.toISOString();
|
|
176
|
+
} catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
166
181
|
export function scan_rules(root?: string | null): [Resource[], string[]] {
|
|
167
182
|
const base = root ?? _project_root();
|
|
168
183
|
const rules_root = path.join(base, 'dist/agent-src', 'rules');
|
|
@@ -256,6 +271,25 @@ function _strCmp(a: string, b: string): number {
|
|
|
256
271
|
return 0;
|
|
257
272
|
}
|
|
258
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Priority in the protocol's 0..1 range.
|
|
276
|
+
*
|
|
277
|
+
* The roadmap that asked for this said to derive it "from the existing `tier`".
|
|
278
|
+
* There is no `tier` on a Resource — measured, not assumed — so it derives from
|
|
279
|
+
* what does exist: the frontmatter `type:` an always-loaded rule carries, then
|
|
280
|
+
* the asset kind. A rule the host loads on every turn outranks one it loads on
|
|
281
|
+
* a trigger, which outranks reference material.
|
|
282
|
+
*
|
|
283
|
+
* PROTOCOL CONFORMANCE ONLY. No shipped host is known to consume `priority` for
|
|
284
|
+
* selection, and this function must not be cited as evidence that one does.
|
|
285
|
+
*/
|
|
286
|
+
export function resourcePriority(resource: Resource): number {
|
|
287
|
+
if (resource.rule_type === 'always') return 1;
|
|
288
|
+
if (resource.rule_type === 'auto') return 0.7;
|
|
289
|
+
if (resource.rule_type === 'manual') return 0.3;
|
|
290
|
+
return resource.kind === 'rule' ? 0.7 : resource.kind === 'context' ? 0.5 : 0.3;
|
|
291
|
+
}
|
|
292
|
+
|
|
259
293
|
/** Project a Resource into MCP `Resource` constructor kwargs. */
|
|
260
294
|
export function to_mcp_resource_meta(resource: Resource): Record<string, unknown> {
|
|
261
295
|
return {
|
|
@@ -263,6 +297,13 @@ export function to_mcp_resource_meta(resource: Resource): Record<string, unknown
|
|
|
263
297
|
name: resource.name,
|
|
264
298
|
description: resource.description,
|
|
265
299
|
mimeType: resource.mime_type,
|
|
300
|
+
// `audience` is the model, not the end user: these are governance
|
|
301
|
+
// assets a host injects, never something a person reads in a client.
|
|
302
|
+
audience: ['assistant'],
|
|
303
|
+
priority: resourcePriority(resource),
|
|
304
|
+
...(resource.last_modified === undefined
|
|
305
|
+
? {}
|
|
306
|
+
: { lastModified: resource.last_modified }),
|
|
266
307
|
_meta: { source: resource.source, kind: resource.kind },
|
|
267
308
|
};
|
|
268
309
|
}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// Per `agents/roadmaps/archive/road-to-mcp-full-coverage.md` §Phase 1 J4 +
|
|
4
4
|
// `docs/contracts/mcp-tool-stub-envelope.md`, both transports log every
|
|
5
5
|
// `tools/call` with `{tool_name, client_id_hash, ts, transport,
|
|
6
|
-
// outcome}`.
|
|
6
|
+
// outcome}`. The stdio-lite surface (`src/cli/mcp/`) appends a sixth,
|
|
7
|
+
// optional `host` field; callers that do not know their client omit it and
|
|
8
|
+
// their records stay byte-identical to the frozen five-field envelope. Payload bodies are never logged; the client identifier is
|
|
7
9
|
// hashed at the server boundary so the queryable store never sees raw
|
|
8
10
|
// identity.
|
|
9
11
|
//
|
|
@@ -107,15 +109,26 @@ export function build_record(options: {
|
|
|
107
109
|
transport: string;
|
|
108
110
|
client_id_hash_value?: string | null | undefined;
|
|
109
111
|
ts?: string | null | undefined;
|
|
112
|
+
host?: string | null | undefined;
|
|
110
113
|
}): Record<string, unknown> {
|
|
111
|
-
const { tool_name, outcome, transport, client_id_hash_value, ts } = options;
|
|
112
|
-
|
|
114
|
+
const { tool_name, outcome, transport, client_id_hash_value, ts, host } = options;
|
|
115
|
+
const record: Record<string, unknown> = {
|
|
113
116
|
tool_name,
|
|
114
117
|
client_id_hash: client_id_hash_value || hash_client_id(),
|
|
115
118
|
ts: ts || _now_iso(),
|
|
116
119
|
transport,
|
|
117
120
|
outcome,
|
|
118
121
|
};
|
|
122
|
+
// `host` is APPENDED, and only when a caller supplies one, so a record
|
|
123
|
+
// written by a caller that does not know its client stays byte-identical
|
|
124
|
+
// to the five-field envelope the stub contract froze. It is a closed
|
|
125
|
+
// vocabulary resolved at the boundary (see `src/cli/mcp/telemetry.ts`),
|
|
126
|
+
// never a free-form string: a record type that cannot hold arbitrary text
|
|
127
|
+
// has no scrubber to fail.
|
|
128
|
+
if (typeof host === 'string' && host !== '') {
|
|
129
|
+
record.host = host;
|
|
130
|
+
}
|
|
131
|
+
return record;
|
|
119
132
|
}
|
|
120
133
|
|
|
121
134
|
/**
|
|
@@ -132,13 +145,15 @@ export function record_call(options: {
|
|
|
132
145
|
transport: string;
|
|
133
146
|
consumer_root?: string | null | undefined;
|
|
134
147
|
client_id_hash_value?: string | null | undefined;
|
|
148
|
+
host?: string | null | undefined;
|
|
135
149
|
}): Record<string, unknown> | null {
|
|
136
|
-
const { tool_name, outcome, transport, consumer_root, client_id_hash_value } = options;
|
|
150
|
+
const { tool_name, outcome, transport, consumer_root, client_id_hash_value, host } = options;
|
|
137
151
|
const record = build_record({
|
|
138
152
|
tool_name,
|
|
139
153
|
outcome,
|
|
140
154
|
transport,
|
|
141
155
|
client_id_hash_value,
|
|
156
|
+
host,
|
|
142
157
|
});
|
|
143
158
|
const target = _resolve_log_path(consumer_root ?? undefined);
|
|
144
159
|
try {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* ./scripts-run src/scripts/orchestration_record \
|
|
11
11
|
* --spawn-count 1 --token-delta -72000 --provenance measured \
|
|
12
12
|
* --tier-chosen lite --tier-source inferred --task-class read-only-fanout \
|
|
13
|
-
* [--tiers sonnet,opus] [--agent-combo implementer,judge] [--wall-clock-ms 18500] [--dispatch-outcome DONE] \
|
|
13
|
+
* [--rules-applied a,b] [--tiers sonnet,opus] [--agent-combo implementer,judge] [--wall-clock-ms 18500] [--dispatch-outcome DONE] \
|
|
14
14
|
* [--first-pass-success true|false] [--escalated true|false] \
|
|
15
15
|
* [--init-tokens 1200] [--payload-hash <hex8-64>] [--lookup-class definition|references|string-existence|report-run] \
|
|
16
16
|
* [--route-taken primitive|subagent|ask] [--budget-hit true|false] [--correctness-match true|false] \
|
|
@@ -141,6 +141,17 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
141
141
|
if (raw === undefined) return undefined;
|
|
142
142
|
return raw.split(',').map((v) => v.trim()).filter(Boolean);
|
|
143
143
|
})(),
|
|
144
|
+
// `--rules-applied=a,b` records the rules the run actually carried.
|
|
145
|
+
// Unlike `--skills-applied`, an OMITTED flag and an empty value mean
|
|
146
|
+
// the same thing here: audit-log-v1 makes `rules_applied` mandatory on
|
|
147
|
+
// every line, so `[]` is how "nothing observed" is spelled, and there
|
|
148
|
+
// is no absent-key slot to distinguish it from. What this flag replaced
|
|
149
|
+
// is the fabricated `['delegation-policy']` this producer wrote on
|
|
150
|
+
// every line until 2026-09-07.
|
|
151
|
+
rules_applied: str(flags, 'rules-applied')
|
|
152
|
+
?.split(',')
|
|
153
|
+
.map((v) => v.trim())
|
|
154
|
+
.filter(Boolean),
|
|
144
155
|
task_size_estimate: int(flags, 'task-size-estimate'),
|
|
145
156
|
wall_clock_ms: int(flags, 'wall-clock-ms'),
|
|
146
157
|
dispatch_tokens: int(flags, 'dispatch-tokens'),
|
package/src/scripts/release.ts
CHANGED
|
@@ -49,8 +49,10 @@
|
|
|
49
49
|
* Idempotency: pass `--resume` to recover from a partial failure. Each
|
|
50
50
|
* step then probes existing state (branch, commit, PR, tag, GitHub
|
|
51
51
|
* Release) and skips work that is already done, instead of erroring out.
|
|
52
|
-
*
|
|
53
|
-
*
|
|
52
|
+
* Re-running from `release/{target}` needs neither the flag nor a clean
|
|
53
|
+
* tree — that position, dirty, is the state `guard_release_curation` leaves
|
|
54
|
+
* behind, and `preflightPosition` accepts it (a dirty `main` still refuses).
|
|
55
|
+
* The
|
|
54
56
|
* two entry points guard against colliding with each other via these same
|
|
55
57
|
* probes plus the CI workflow's concurrency group — whichever starts
|
|
56
58
|
* second sees the other's in-progress state and skips or refuses cleanly.
|
|
@@ -124,6 +126,7 @@ import {
|
|
|
124
126
|
derive_category_hits,
|
|
125
127
|
render_derived_head_values,
|
|
126
128
|
} from './_lib/release_highlights.js';
|
|
129
|
+
import { preflightPosition } from './_lib/release_position.js';
|
|
127
130
|
import {
|
|
128
131
|
RELEASE_HEAD_DEFAULT,
|
|
129
132
|
extract_changelog_section,
|
|
@@ -784,17 +787,17 @@ export function assert_scheduled_deprecations_clear(
|
|
|
784
787
|
/**
|
|
785
788
|
* Fail fast on conditions that would break the release mid-flight.
|
|
786
789
|
*
|
|
787
|
-
*
|
|
790
|
+
* The start position — which branch, and whether a dirty tree is tolerated —
|
|
791
|
+
* is `preflightPosition` in `_lib/release_position.ts`, and it is NO LONGER
|
|
792
|
+
* gated on `--resume`:
|
|
793
|
+
* `release/{target}` is where `guard_release_curation` leaves the operator, so
|
|
794
|
+
* refusing it there made the guard's own remedy unreachable. Read that
|
|
795
|
+
* function's header for the measurement.
|
|
788
796
|
*
|
|
789
|
-
*
|
|
790
|
-
*
|
|
791
|
-
* you on the release branch).
|
|
792
|
-
* - The target-tag-exists check is dropped — execute() probes for existing
|
|
793
|
-
* tags/releases and skips them.
|
|
797
|
+
* `--resume` still relaxes ONE invariant here: the target-tag-exists check is
|
|
798
|
+
* dropped, because execute() probes for existing tags/releases and skips them.
|
|
794
799
|
*
|
|
795
|
-
*
|
|
796
|
-
* enforced, so resuming has the same starting posture as a fresh run; only
|
|
797
|
-
* step-level outcomes differ.
|
|
800
|
+
* gh auth and `main` in-sync with origin are enforced either way.
|
|
798
801
|
*
|
|
799
802
|
* `opts.ci` swaps the gh-auth probe (see below) for the CI-mode variant —
|
|
800
803
|
* everything else is identical between the two entry points.
|
|
@@ -832,20 +835,17 @@ function preflight(target: string, opts: { resume?: boolean; ci?: boolean } = {}
|
|
|
832
835
|
|
|
833
836
|
const branch = git(['rev-parse', '--abbrev-ref', 'HEAD'], { capture: true });
|
|
834
837
|
const release_branch = `release/${target}`;
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
die(`release must run from '${MAIN_BRANCH}', currently on '${branch}'`);
|
|
838
|
+
const position = preflightPosition({
|
|
839
|
+
branch,
|
|
840
|
+
mainBranch: MAIN_BRANCH,
|
|
841
|
+
releaseBranch: release_branch,
|
|
842
|
+
porcelain: git(['status', '--porcelain'], { capture: true }),
|
|
843
|
+
});
|
|
844
|
+
if (!position.proceed) {
|
|
845
|
+
die(position.message ?? `release cannot start from '${branch}'`);
|
|
844
846
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
if (porcelain) {
|
|
848
|
-
die('working tree is not clean; commit or stash first');
|
|
847
|
+
if (position.notice) {
|
|
848
|
+
process.stdout.write(`\u26a0\ufe0f ${position.notice}\n`);
|
|
849
849
|
}
|
|
850
850
|
|
|
851
851
|
// --force lets the remote's tag positions win over stale local tags.
|