@event4u/agent-config 14.20.0 → 14.22.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 +441 -0
- package/CREDITS.md +37 -0
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +10 -0
- package/dist/agent-src/commands/analyze/inbox.md +381 -6
- package/dist/agent-src/commands/analyze/{reference-repo.md → repo.md} +251 -34
- package/dist/agent-src/commands/analyze/roadmap-repos.md +129 -0
- package/dist/agent-src/commands/analyze.md +12 -7
- package/dist/agent-src/commands/feature/plan.md +55 -16
- package/dist/agent-src/commands/feature/refactor.md +8 -3
- package/dist/agent-src/commands/humanize.md +48 -4
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +22 -1
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +4 -1
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +35 -2
- package/dist/agent-src/contexts/execution/terminal-states.md +4 -1
- package/dist/agent-src/rules/evaluator-independence.md +4 -4
- package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
- package/dist/agent-src/rules/external-reference-deep-dive.md +2 -2
- package/dist/agent-src/rules/token-efficiency.md +26 -3
- package/dist/agent-src/scripts/_cli_entry.ts +51 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
- package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/dist/agent-src/scripts/stubs_due.ts +4 -18
- package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
- package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +27 -11
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
- package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
- package/dist/agent-src/skills/technical-specification/SKILL.md +4 -0
- package/dist/agent-src/templates/agent-settings.md +0 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/features.md +5 -0
- package/dist/agent-src/templates/roadmaps.md +15 -0
- package/dist/cli-delegate/_cli_entry.js +8 -0
- package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
- package/dist/cli-delegate/archival_sweep.js +8 -0
- package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
- package/dist/cli-delegate/check_update_banner.js +923 -0
- package/dist/cli-delegate/{chunk-KPBWFOPZ.js → chunk-2FTMENJL.js} +194 -37
- package/dist/cli-delegate/{chunk-PI2GU3DP.js → chunk-2TAQ2K4V.js} +4 -1
- package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
- package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
- package/dist/cli-delegate/{chunk-C6IPZRDA.js → chunk-BIXEDQPZ.js} +7 -1
- package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
- package/dist/cli-delegate/{chunk-NUMQHLS5.js → chunk-J3RFNKG4.js} +21 -2
- package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
- package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
- package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
- package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
- package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
- package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
- package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
- package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
- package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
- package/dist/cli-delegate/cmd_conformance.js +19 -5
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +27 -3
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- 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/cli-delegate/dashboard_mode.js +16 -0
- package/dist/cli-delegate/gate_budget.js +24 -0
- package/dist/cli-delegate/gate_execute.js +25 -0
- package/dist/cli-delegate/guarded_baseline.js +18 -0
- package/dist/cli-delegate/resume_probe.js +22 -0
- package/dist/cli-delegate/roadmap_gates.js +638 -0
- package/dist/cli-delegate/roadmap_set_step.js +173 -0
- package/dist/cli-delegate/stub_queue.js +47 -0
- package/dist/cli-delegate/stubs_due.js +38 -0
- package/dist/cli-delegate/ticket_bundles.js +59 -0
- package/dist/cli-delegate/update_roadmap_progress.js +60 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +60 -29
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -6
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -5
- package/dist/hooks/dispatch.js +1522 -1008
- package/dist/install/claudeLayerCarriage.js +237 -0
- package/dist/install/claudePathsPlan.js +79 -0
- package/dist/install/hostLayerFingerprint.js +1 -1
- package/dist/install/install.mjs +10 -1
- package/dist/install/partitionEligibility.js +231 -117
- package/dist/install/ruleLayerPartition.js +10 -7
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +39376 -38902
- package/dist/scripts/ai_council/content_ceiling.js +63 -0
- package/dist/scripts/ai_council/seat_policy.js +106 -0
- package/dist/scripts/ai_council/transport_resolver.js +21 -0
- package/dist/scripts/condense.js +32 -25
- package/docs/MIGRATION.md +49 -1
- package/docs/contracts/settings-classes.md +1 -1
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
- package/llms.txt +14 -14
- package/package.json +7 -4
- package/src/agent-src/scripts/_cli_entry.ts +51 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/src/agent-src/scripts/roadmap_gates.ts +4 -20
- package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/src/agent-src/scripts/stubs_due.ts +4 -18
- package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/src/config/continuity-surface.json +509 -0
- package/src/config/estate-count-budget.json +1 -1
- package/src/config/gate-coverage.yml +29 -8
- package/src/config/gate-violation-baselines.json +8 -8
- package/src/config/gitignore-block.txt +19 -0
- package/src/config/host-injection-effect.json +73 -0
- package/src/config/packed-binary-manifest.json +33 -0
- package/src/config/preamble-payload-budget.json +112 -105
- package/src/config/publish-surface.json +4 -2
- package/src/config/release-gate-locality.yml +4 -3
- package/src/install/claudeLayerCarriage.ts +273 -0
- package/src/install/claudePathsPlan.ts +76 -0
- package/src/install/hostLayerFingerprint.ts +1 -1
- package/src/install/partitionEligibility.ts +261 -127
- package/src/install/ruleLayerPartition.ts +19 -23
- package/src/scripts/_dispatch.bash +77 -27
- package/src/scripts/_lib/auto_dispatch.ts +2 -2
- package/src/scripts/_lib/blocked_by_marker.ts +189 -0
- package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
- package/src/scripts/_lib/continuity_surface.ts +296 -0
- package/src/scripts/_lib/corpus_manifest.ts +26 -9
- package/src/scripts/_lib/hook_settings.ts +1 -1
- package/src/scripts/_lib/host_capability.ts +38 -1
- package/src/scripts/_lib/injection_effect.ts +151 -0
- package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
- package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
- package/src/scripts/_lib/pin_resolver.ts +13 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
- package/src/scripts/_lib/release_highlights.ts +19 -211
- package/src/scripts/_lib/release_material.ts +27 -77
- package/src/scripts/_lib/retired_status.ts +74 -0
- package/src/scripts/_lib/skill_catalogue.ts +156 -21
- package/src/scripts/_lib/structured_ask.ts +117 -0
- package/src/scripts/_lib/subagent_capsule.ts +117 -8
- package/src/scripts/_lib/tty_prompt.ts +76 -0
- package/src/scripts/ai_council/api_transport.ts +168 -0
- package/src/scripts/ai_council/clients.ts +58 -63
- package/src/scripts/ai_council/config.ts +13 -0
- package/src/scripts/ai_council/content_ceiling.ts +79 -0
- package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
- package/src/scripts/ai_council/jury_aggregate.ts +178 -0
- package/src/scripts/ai_council/prompts.ts +9 -0
- package/src/scripts/ai_council/seat_policy.ts +137 -0
- package/src/scripts/ai_council/status_surface.ts +79 -0
- package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
- package/src/scripts/ai_council/transport_resolver.ts +59 -1
- package/src/scripts/ai_tells_rules.ts +446 -5
- package/src/scripts/ask_block_census.ts +499 -0
- package/src/scripts/bench_humanizer_eval.ts +264 -40
- package/src/scripts/bench_prose_tells_fp.ts +198 -0
- package/src/scripts/capture_skill_catalogue.ts +24 -11
- package/src/scripts/check_branch_work_committed.ts +234 -0
- package/src/scripts/check_candidate_lines.ts +499 -0
- package/src/scripts/check_continuity_surface.ts +475 -0
- package/src/scripts/check_dependency_floors.ts +13 -6
- package/src/scripts/check_estate_count.ts +40 -79
- package/src/scripts/check_pack_size.ts +263 -24
- package/src/scripts/check_release_highlights.ts +0 -157
- package/src/scripts/check_roadmap_trackable.ts +19 -16
- package/src/scripts/check_rule_layer_partition.ts +54 -34
- package/src/scripts/check_single_delivery.ts +97 -31
- package/src/scripts/check_update_banner.ts +13 -0
- package/src/scripts/code_graph/build.ts +138 -15
- package/src/scripts/code_graph/cli.ts +76 -8
- package/src/scripts/code_graph/extract.ts +11 -0
- package/src/scripts/code_graph/grammar_probe.ts +43 -0
- package/src/scripts/code_graph/loader.ts +88 -14
- package/src/scripts/code_graph/query.ts +121 -11
- package/src/scripts/code_graph/resolution_tiers.ts +238 -0
- package/src/scripts/code_graph/sqlite_store.ts +247 -6
- package/src/scripts/code_graph/types.ts +66 -1
- package/src/scripts/code_graph/validate.ts +23 -0
- package/src/scripts/condense.ts +34 -22
- package/src/scripts/corpus_manifest.ts +5 -5
- package/src/scripts/council_cli.ts +29 -39
- package/src/scripts/count_intent_disagreement.ts +426 -0
- package/src/scripts/detect_ai_tells.ts +142 -20
- package/src/scripts/handoff_context_hook.ts +79 -0
- package/src/scripts/harvest_reference_tokens.ts +698 -0
- package/src/scripts/hook_effect_doctor.ts +82 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +94 -12
- package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
- package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
- package/src/scripts/hooks/concern_registry.ts +6 -2
- package/src/scripts/hooks/delegation_nudge_hook.ts +45 -1
- package/src/scripts/hooks/host_semantics.ts +1 -1
- package/src/scripts/hooks/one_question_per_ask_hook.ts +191 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
- package/src/scripts/hooks/rule_inject_hook.ts +1 -1
- package/src/scripts/hooks/skill_route_hook.ts +11 -4
- package/src/scripts/inbox_source_census.ts +322 -0
- package/src/scripts/install-hooks.sh +134 -0
- package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
- package/src/scripts/lint_plan_risk_register.ts +13 -17
- package/src/scripts/lint_roadmap_blockers.ts +51 -1
- package/src/scripts/mcp_server/suggest_skill.ts +72 -0
- package/src/scripts/mcp_server/tools.ts +2 -56
- package/src/scripts/media/lib/adapter-contract.md +7 -2
- package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
- package/src/scripts/migrate_command_suggestions.ts +1 -1
- package/src/scripts/probe_unblocked_ask.ts +135 -2
- package/src/scripts/rdp_candidates_delta.ts +276 -0
- package/src/scripts/rdp_quality_eval.ts +217 -19
- package/src/scripts/release.ts +26 -71
- package/src/scripts/release_drill.ts +6 -10
- package/src/scripts/release_env.ts +1 -0
- package/src/scripts/release_mix_taxonomy.json +3 -2
- package/src/scripts/release_publication.ts +34 -29
- package/src/scripts/report_host_injection_effect.ts +189 -0
- package/src/scripts/report_layer_overlap.ts +41 -0
- package/src/scripts/report_skill_menu_census.ts +341 -0
- package/src/scripts/self_review_gate.ts +409 -15
- package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
- package/src/vendor/grammars/README.md +61 -0
- package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/src/scripts/_lib/carrier_status.ts +0 -118
- package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
- package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
|
@@ -54,12 +54,16 @@ const TYPES = ['rules', 'skills', 'commands', 'personas', 'user-types', 'agents'
|
|
|
54
54
|
export function overlapFindings(homeDir: string, projectRoot: string): string[] {
|
|
55
55
|
const findings: string[] = [];
|
|
56
56
|
for (const type of TYPES) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
// `commands` keys on the POSIX SUBPATH, matching
|
|
58
|
+
// `check_single_delivery`'s own key. Top-level names are the CLUSTER, and
|
|
59
|
+
// since per-name withholding (2026-09-07) the project layer holds exactly
|
|
60
|
+
// the commands the host lacks — which can share a cluster with commands the
|
|
61
|
+
// host has. Observed the same day: `analyze/` in both layers over two
|
|
62
|
+
// disjoint command sets, reported here as `commands=1`. This notice and
|
|
63
|
+
// that gate must not disagree about the same question.
|
|
64
|
+
const g = _entries(path.join(homeDir, '.claude', type), type === 'commands');
|
|
65
|
+
const p = _entries(path.join(projectRoot, '.claude', type), type === 'commands');
|
|
66
|
+
if (g === null || p === null) {
|
|
63
67
|
continue;
|
|
64
68
|
}
|
|
65
69
|
const gset = new Set(g);
|
|
@@ -69,6 +73,46 @@ export function overlapFindings(homeDir: string, projectRoot: string): string[]
|
|
|
69
73
|
return findings;
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
/**
|
|
77
|
+
* One layer's entry names — top level, or every `*.md` subpath when `recursive`.
|
|
78
|
+
*
|
|
79
|
+
* `null` means the directory is not readable, which the caller must treat as "skip
|
|
80
|
+
* this type": one layer absent means nothing is doubled there.
|
|
81
|
+
*/
|
|
82
|
+
function _entries(dir: string, recursive: boolean): string[] | null {
|
|
83
|
+
if (!recursive) {
|
|
84
|
+
try {
|
|
85
|
+
return fs.readdirSync(dir);
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let top: fs.Dirent[];
|
|
91
|
+
try {
|
|
92
|
+
top = fs.readdirSync(dir, { withFileTypes: true });
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const out: string[] = [];
|
|
97
|
+
const walk = (kids: fs.Dirent[], abs: string, rel: string): void => {
|
|
98
|
+
for (const k of kids) {
|
|
99
|
+
const childRel = rel === '' ? k.name : `${rel}/${k.name}`;
|
|
100
|
+
if (k.isDirectory()) {
|
|
101
|
+
try {
|
|
102
|
+
walk(fs.readdirSync(path.join(abs, k.name), { withFileTypes: true }), path.join(abs, k.name), childRel);
|
|
103
|
+
} catch {
|
|
104
|
+
// An unreadable subdirectory contributes no names; the layer
|
|
105
|
+
// itself was readable, so the type stays compared.
|
|
106
|
+
}
|
|
107
|
+
} else if (k.name.endsWith('.md') && k.name !== 'README.md') {
|
|
108
|
+
out.push(childRel);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
walk(top, dir, '');
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
72
116
|
/** Render the notice, or `null` when there is nothing to say. */
|
|
73
117
|
export function overlapNotice(homeDir: string, projectRoot: string): string | null {
|
|
74
118
|
const findings = overlapFindings(homeDir, projectRoot);
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary-payload eligibility — the mechanically verified predicate that
|
|
3
|
+
* reopened the `binary` class.
|
|
4
|
+
*
|
|
5
|
+
* WHAT THIS IS, SAID PLAINLY.
|
|
6
|
+
*
|
|
7
|
+
* `f573d628c` closed `binary` as a hard zero on 2026-09-07. This REOPENS it,
|
|
8
|
+
* 24 hours later, and that IS A WEAKENING OF A CONTROL. An AI council
|
|
9
|
+
* (2026-09-08, 2 seats, unanimous, deep tier) authorised it, and one seat
|
|
10
|
+
* explicitly refused the framing that this is merely "correcting an
|
|
11
|
+
* over-generalisation" — that distinction is cosmetic. The control was closed
|
|
12
|
+
* for the wrong reason, so reopening it is a weakening: justified, and still a
|
|
13
|
+
* weakening. It is written down here rather than softened because a control
|
|
14
|
+
* that gets quietly relaxed is worse than one that never existed.
|
|
15
|
+
*
|
|
16
|
+
* WHY THE CLOSURE WAS WRONG.
|
|
17
|
+
*
|
|
18
|
+
* Decision A over-generalised. Its reasoning was "the three observed binaries
|
|
19
|
+
* are unused, therefore no binary can ever be required" — an invalid
|
|
20
|
+
* generalisation from an observed sample to a universal rule. The vendored
|
|
21
|
+
* tree-sitter grammars are the direct counterexample: bytes the production path
|
|
22
|
+
* loads and the engine requires. A textual placeholder was lossless for three
|
|
23
|
+
* PNGs nothing decoded; for a grammar it does not degrade a fixture, it breaks
|
|
24
|
+
* the engine.
|
|
25
|
+
*
|
|
26
|
+
* WHAT REPLACES THE HARD ZERO — AND WHAT DOES NOT.
|
|
27
|
+
*
|
|
28
|
+
* Not a prose exception. A binary is admitted only when EVERY condition below
|
|
29
|
+
* is verified mechanically, on the bytes that are actually in the tarball:
|
|
30
|
+
*
|
|
31
|
+
* 1. exactly one manifest entry for the path
|
|
32
|
+
* 2. no wildcard or directory grant anywhere in the manifest
|
|
33
|
+
* 3. the packed tarball really contains the path
|
|
34
|
+
* 4. sha256(bytes) equals the manifest hash
|
|
35
|
+
* 5. size equals the manifest size
|
|
36
|
+
* 6. kind is `tree-sitter-wasm-grammar`
|
|
37
|
+
* 7. WASM magic and version are valid
|
|
38
|
+
* 8. the PRODUCTION loader reports the manifest's grammar_id
|
|
39
|
+
* 9. the PRODUCTION loader reports the manifest's ABI
|
|
40
|
+
* 10. a defined invalid-header mutation of the bytes is REJECTED
|
|
41
|
+
* 11. a defined unsupported-ABI expectation is REJECTED
|
|
42
|
+
*
|
|
43
|
+
* 8 and 9 go through `probeGrammarFile`, the engine's own loader, deliberately:
|
|
44
|
+
* a second implementation inside the gate could accept bytes the engine
|
|
45
|
+
* rejects, which is exactly the gap that would let a broken grammar ship while
|
|
46
|
+
* the gate called it valid.
|
|
47
|
+
*
|
|
48
|
+
* 10 and 11 run on every invocation, not in a separate test, because a
|
|
49
|
+
* predicate whose refusals nobody exercises is a predicate nobody has checked.
|
|
50
|
+
*
|
|
51
|
+
* `archive` IS NOT REOPENED.
|
|
52
|
+
*
|
|
53
|
+
* This collision produced evidence about grammars and none whatsoever about
|
|
54
|
+
* archives. `archive` stays a hard zero with no manifest path, and this module
|
|
55
|
+
* offers none.
|
|
56
|
+
*
|
|
57
|
+
* ELIGIBILITY NEVER TOUCHES THE MEASUREMENT.
|
|
58
|
+
*
|
|
59
|
+
* The gate reports `observed` as the true count and byte total on every run.
|
|
60
|
+
* Compliance is `observed <= allowed`, where `allowed` is the number of entries
|
|
61
|
+
* that passed every condition above — never `observed` minus something excused.
|
|
62
|
+
* A file that fails one condition lowers `allowed`; it never disappears from
|
|
63
|
+
* `observed`.
|
|
64
|
+
*/
|
|
65
|
+
import { createHash } from 'node:crypto';
|
|
66
|
+
import * as fs from 'node:fs';
|
|
67
|
+
import * as path from 'node:path';
|
|
68
|
+
|
|
69
|
+
/** One manifest row. Path-exact; `why` is documentation, never a condition. */
|
|
70
|
+
export interface PackedBinaryEntry {
|
|
71
|
+
readonly path: string;
|
|
72
|
+
readonly sha256: string;
|
|
73
|
+
readonly size: number;
|
|
74
|
+
readonly kind: string;
|
|
75
|
+
readonly grammar_id: string;
|
|
76
|
+
readonly abi: number;
|
|
77
|
+
readonly why?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface PackedBinaryManifest {
|
|
81
|
+
readonly schema_version: number;
|
|
82
|
+
readonly entries: readonly PackedBinaryEntry[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The one kind this predicate knows how to verify. */
|
|
86
|
+
export const VERIFIABLE_KIND = 'tree-sitter-wasm-grammar';
|
|
87
|
+
|
|
88
|
+
/** What the production loader reported for a file. */
|
|
89
|
+
export interface GrammarProbe {
|
|
90
|
+
readonly abi: number;
|
|
91
|
+
readonly grammar_id: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A per-file verdict, with the failing condition named. */
|
|
95
|
+
export interface EligibilityVerdict {
|
|
96
|
+
readonly path: string;
|
|
97
|
+
readonly eligible: boolean;
|
|
98
|
+
/** Every condition that failed, in predicate order. Empty iff eligible. */
|
|
99
|
+
readonly failures: readonly string[];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** WASM preamble: `\0asm` followed by little-endian version 1. */
|
|
103
|
+
const WASM_MAGIC = [0x00, 0x61, 0x73, 0x6d] as const;
|
|
104
|
+
const WASM_VERSION = [0x01, 0x00, 0x00, 0x00] as const;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Condition 7, on its own so the header mutation in condition 10 has something
|
|
108
|
+
* specific to defeat.
|
|
109
|
+
*/
|
|
110
|
+
export function wasmMagicAndVersionValid(bytes: Uint8Array): boolean {
|
|
111
|
+
if (bytes.length < 8) return false;
|
|
112
|
+
for (let i = 0; i < 4; i += 1) if (bytes[i] !== WASM_MAGIC[i]) return false;
|
|
113
|
+
for (let i = 0; i < 4; i += 1) if (bytes[4 + i] !== WASM_VERSION[i]) return false;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The DEFINED invalid-header mutation for condition 10.
|
|
119
|
+
*
|
|
120
|
+
* One byte of the magic, flipped. Deliberately NOT `corrupt(bytes)`: arbitrary
|
|
121
|
+
* corruption can land in a custom section and leave a still-valid module, or
|
|
122
|
+
* hit padding and change nothing, so a test built on it can pass while proving
|
|
123
|
+
* nothing. This targets a property the predicate actually validates.
|
|
124
|
+
*/
|
|
125
|
+
export function definedInvalidHeaderMutation(bytes: Uint8Array): Uint8Array {
|
|
126
|
+
const out = Uint8Array.from(bytes);
|
|
127
|
+
if (out.length > 1) out[1] = (out[1] as number) ^ 0xff;
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The DEFINED unsupported-ABI expectation for condition 11.
|
|
133
|
+
*
|
|
134
|
+
* The ABI is not a byte at a known offset — it is what the loader reports after
|
|
135
|
+
* parsing the module — so this mutates the EXPECTATION rather than the bytes:
|
|
136
|
+
* an entry claiming an ABI the grammar does not have must be refused. Same
|
|
137
|
+
* validated property, targeted from the side where it can be stated exactly.
|
|
138
|
+
*/
|
|
139
|
+
export function definedUnsupportedAbiEntry(entry: PackedBinaryEntry): PackedBinaryEntry {
|
|
140
|
+
return { ...entry, abi: entry.abi + 1 };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Condition 2, evaluated over the whole manifest rather than per file. */
|
|
144
|
+
export function wildcardGrants(manifest: PackedBinaryManifest): string[] {
|
|
145
|
+
return manifest.entries
|
|
146
|
+
.filter((e) => e.path.includes('*') || e.path.includes('?') || e.path.endsWith('/'))
|
|
147
|
+
.map((e) => e.path);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Paths carrying more than one row — condition 1's uniqueness half. */
|
|
151
|
+
export function duplicatePaths(manifest: PackedBinaryManifest): string[] {
|
|
152
|
+
const seen = new Map<string, number>();
|
|
153
|
+
for (const e of manifest.entries) seen.set(e.path, (seen.get(e.path) ?? 0) + 1);
|
|
154
|
+
return [...seen.entries()].filter(([, n]) => n > 1).map(([p]) => p);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Evaluate every condition for one packed file.
|
|
159
|
+
*
|
|
160
|
+
* `probe` is injected so the caller decides how the production loader is
|
|
161
|
+
* reached — in the gate it is a child process, in a test it can be a stub that
|
|
162
|
+
* returns a wrong ABI. `null` means the loader could not answer, which fails
|
|
163
|
+
* conditions 8 and 9 rather than passing them by default.
|
|
164
|
+
*/
|
|
165
|
+
export function evaluateEntry(options: {
|
|
166
|
+
readonly filePath: string;
|
|
167
|
+
readonly packedPaths: ReadonlySet<string>;
|
|
168
|
+
readonly manifest: PackedBinaryManifest;
|
|
169
|
+
readonly bytes: Uint8Array | null;
|
|
170
|
+
readonly probe: GrammarProbe | null;
|
|
171
|
+
}): EligibilityVerdict {
|
|
172
|
+
const { filePath, packedPaths, manifest, bytes, probe } = options;
|
|
173
|
+
const failures: string[] = [];
|
|
174
|
+
|
|
175
|
+
const rows = manifest.entries.filter((e) => e.path === filePath);
|
|
176
|
+
if (rows.length === 0) failures.push('no manifest entry for this path');
|
|
177
|
+
if (rows.length > 1) failures.push('more than one manifest entry for this path');
|
|
178
|
+
const wild = wildcardGrants(manifest);
|
|
179
|
+
if (wild.length > 0) failures.push(`manifest carries wildcard or directory grants: ${wild.join(', ')}`);
|
|
180
|
+
if (!packedPaths.has(filePath)) failures.push('path is not in the packed tarball');
|
|
181
|
+
|
|
182
|
+
const entry = rows[0];
|
|
183
|
+
if (entry === undefined) return { path: filePath, eligible: false, failures };
|
|
184
|
+
|
|
185
|
+
if (bytes === null) {
|
|
186
|
+
failures.push('bytes could not be read');
|
|
187
|
+
return { path: filePath, eligible: false, failures };
|
|
188
|
+
}
|
|
189
|
+
const digest = createHash('sha256').update(bytes).digest('hex');
|
|
190
|
+
if (digest !== entry.sha256) failures.push(`sha256 mismatch: ${digest} != ${entry.sha256}`);
|
|
191
|
+
if (bytes.length !== entry.size) failures.push(`size mismatch: ${String(bytes.length)} != ${String(entry.size)}`);
|
|
192
|
+
if (entry.kind !== VERIFIABLE_KIND) failures.push(`kind is not ${VERIFIABLE_KIND}: ${entry.kind}`);
|
|
193
|
+
if (!wasmMagicAndVersionValid(bytes)) failures.push('WASM magic or version invalid');
|
|
194
|
+
|
|
195
|
+
if (probe === null) {
|
|
196
|
+
failures.push('the production loader could not read this file');
|
|
197
|
+
} else {
|
|
198
|
+
if (probe.grammar_id !== entry.grammar_id) {
|
|
199
|
+
failures.push(`grammar_id mismatch: loader says ${probe.grammar_id}, manifest says ${entry.grammar_id}`);
|
|
200
|
+
}
|
|
201
|
+
if (probe.abi !== entry.abi) {
|
|
202
|
+
failures.push(`ABI mismatch: loader says ${String(probe.abi)}, manifest says ${String(entry.abi)}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Conditions 10 and 11 — the predicate refusing what it must refuse. Run
|
|
207
|
+
// here, on every invocation, so a refusal path is never merely asserted in
|
|
208
|
+
// a test somebody can skip.
|
|
209
|
+
if (wasmMagicAndVersionValid(definedInvalidHeaderMutation(bytes))) {
|
|
210
|
+
failures.push('self-check failed: a defined invalid-header mutation was NOT rejected');
|
|
211
|
+
}
|
|
212
|
+
if (probe !== null && probe.abi === definedUnsupportedAbiEntry(entry).abi) {
|
|
213
|
+
failures.push('self-check failed: a defined unsupported-ABI expectation was NOT rejected');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { path: filePath, eligible: failures.length === 0, failures };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Read and shape-check the manifest. Throws rather than degrading to empty. */
|
|
220
|
+
export function loadManifest(repoRoot: string): PackedBinaryManifest {
|
|
221
|
+
const p = path.join(repoRoot, 'src', 'config', 'packed-binary-manifest.json');
|
|
222
|
+
const raw = JSON.parse(fs.readFileSync(p, 'utf-8')) as PackedBinaryManifest;
|
|
223
|
+
if (!Array.isArray(raw.entries)) throw new Error(`packed-binary-manifest: entries must be an array (${p})`);
|
|
224
|
+
return raw;
|
|
225
|
+
}
|
|
@@ -253,10 +253,23 @@ function _parse_cli(argv: string[]): { cwd: string; installed: string; forward:
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
// Script entry — mirrors the Python `if __name__ == "__main__"` block.
|
|
256
|
+
// `build:delegate-aux` bundles this file with `--splitting`, which can move the
|
|
257
|
+
// module body into a shared chunk: `import.meta.url` then names the CHUNK, the
|
|
258
|
+
// URL comparison below never matches, and the entry silently no-ops. Inside that
|
|
259
|
+
// bundle the invoked file name is the reliable signal (same pattern as
|
|
260
|
+
// src/scripts/_cli/cmd_migrate.ts). Defined by that build only.
|
|
261
|
+
declare const __AGENT_CONFIG_CLI_DELEGATE__: boolean | undefined;
|
|
256
262
|
function _isCliEntry(): boolean {
|
|
257
263
|
if (process.argv[1] === undefined) {
|
|
258
264
|
return false;
|
|
259
265
|
}
|
|
266
|
+
if (typeof __AGENT_CONFIG_CLI_DELEGATE__ !== 'undefined' && __AGENT_CONFIG_CLI_DELEGATE__) {
|
|
267
|
+
// A miss falls THROUGH to the URL comparison below, which is the
|
|
268
|
+
// symlinked-invocation case that fallback exists for.
|
|
269
|
+
if (path.basename(process.argv[1], '.js') === 'pin_resolver') {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
260
273
|
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
261
274
|
if (import.meta.url === argvUrl) {
|
|
262
275
|
return true;
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* of them resolves here, so the substitution is STATED rather than hidden.
|
|
19
19
|
*
|
|
20
20
|
* · `agent-config code-graph` — the native code-graph engine (ADR-124). It
|
|
21
|
-
*
|
|
21
|
+
* shipped `hooks.code_graph.enabled: false` (a flag retired 2026-09-07)
|
|
22
22
|
* (`src/config/agent-settings.template.yml:1373-1374`), and
|
|
23
23
|
* `agent-config code-graph detect` in this checkout answers
|
|
24
24
|
* `no code-graph source detected`. There is no index to select against.
|
|
@@ -25,15 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { spawnSync } from 'node:child_process';
|
|
27
27
|
|
|
28
|
-
import {
|
|
29
|
-
CURATED_HEAD_INSTRUCTION,
|
|
30
|
-
MIX_RESPONSE_MARKER,
|
|
31
|
-
MIX_RESPONSE_PLACEHOLDERS,
|
|
32
|
-
PROMISE_OUTCOMES,
|
|
33
|
-
PROMISE_PHRASE,
|
|
34
|
-
PROMISE_READBACK_MARKER,
|
|
35
|
-
extract_changelog_section,
|
|
36
|
-
} from './release_material.js';
|
|
28
|
+
import { CURATED_HEAD_INSTRUCTION } from './release_material.js';
|
|
37
29
|
|
|
38
30
|
/** The five curated labels, in the order an operator reads them. */
|
|
39
31
|
export const HEAD_LABELS: readonly string[] = [
|
|
@@ -406,7 +398,6 @@ export function publication_blockers(
|
|
|
406
398
|
sectionBody: string,
|
|
407
399
|
version: string,
|
|
408
400
|
where = '`main`',
|
|
409
|
-
mix: MixObligation | null = null,
|
|
410
401
|
): string[] {
|
|
411
402
|
const out: string[] = [];
|
|
412
403
|
if (sectionBody.includes(DERIVED_MARKER)) {
|
|
@@ -423,13 +414,15 @@ export function publication_blockers(
|
|
|
423
414
|
'comment line from the section and re-run.',
|
|
424
415
|
);
|
|
425
416
|
}
|
|
426
|
-
out.push(...mix_response_blockers(sectionBody, version, where, mix));
|
|
427
417
|
return out;
|
|
428
418
|
}
|
|
429
419
|
|
|
430
420
|
/**
|
|
431
421
|
* Everything that makes a whole SECTION unpublishable — the head-level
|
|
432
|
-
* blockers above plus the
|
|
422
|
+
* blockers above plus the tests footer, which only exists for a full section.
|
|
423
|
+
*
|
|
424
|
+
* It was "the two obligations" until 2026-09-07: the governance-versus-product
|
|
425
|
+
* written answer was the other, and ADR-261 deleted it. One remains.
|
|
433
426
|
*
|
|
434
427
|
* Two levels and not one, because `publication_blockers` is legitimately called
|
|
435
428
|
* with a bare curated head (the prefill tests do exactly that), and a head
|
|
@@ -450,9 +443,8 @@ export function section_publication_blockers(
|
|
|
450
443
|
sectionBody: string,
|
|
451
444
|
version: string,
|
|
452
445
|
where = '`main`',
|
|
453
|
-
mix: MixObligation | null = null,
|
|
454
446
|
): string[] {
|
|
455
|
-
const out = publication_blockers(sectionBody, version, where
|
|
447
|
+
const out = publication_blockers(sectionBody, version, where);
|
|
456
448
|
if (!TEST_FOOTER_RE.test(sectionBody)) {
|
|
457
449
|
out.push(
|
|
458
450
|
`the ${version} section carries no \`Tests: N (+M since PREV)\` footer. ` +
|
|
@@ -479,208 +471,24 @@ export function section_publication_blockers(
|
|
|
479
471
|
const TEST_FOOTER_RE = /^Tests: \d+/mu;
|
|
480
472
|
|
|
481
473
|
/**
|
|
482
|
-
* The measured governance-versus-product level, as the writer
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
* `
|
|
474
|
+
* The measured governance-versus-product level, as the writer receives it.
|
|
475
|
+
*
|
|
476
|
+
* A plain record and not the measurement itself: only the callers have git to
|
|
477
|
+
* measure with, and keeping `measure_release_mix`'s git access out of this
|
|
478
|
+
* module keeps it importable by the guards.
|
|
479
|
+
*
|
|
480
|
+
* It carried a `triggered` boolean until ADR-261, when the obligation that
|
|
481
|
+
* boolean gated was deleted. It was removed rather than kept with a rationale:
|
|
482
|
+
* the first attempt at this docblock claimed a reader of the published line
|
|
483
|
+
* could see the verdict, and a neutral review falsified that — `triggered` was
|
|
484
|
+
* set once and read nowhere, and `render_mix_response` never received it. The
|
|
485
|
+
* two counts in `level` are what the line publishes, and the taxonomy's own
|
|
486
|
+
* verdict stays where it is computed and printed, in `measure_release_mix`.
|
|
489
487
|
*/
|
|
490
488
|
export interface MixObligation {
|
|
491
|
-
triggered: boolean;
|
|
492
489
|
level: string;
|
|
493
490
|
}
|
|
494
491
|
|
|
495
|
-
/** Minimum written characters after the marker — a bare marker is not an answer. */
|
|
496
|
-
export const MIX_RESPONSE_MIN_CHARS = 40;
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* The ONE predicate for the governance-versus-product obligation.
|
|
500
|
-
*
|
|
501
|
-
* `check_release_highlights` refuses this from the CI side and
|
|
502
|
-
* `guard_release_branch_push` from the local side, and until 2026-09-05 only
|
|
503
|
-
* the first of the two knew the obligation existed. That asymmetry is what the
|
|
504
|
-
* contract itself recorded — *"the earliest refusal for this one obligation is
|
|
505
|
-
* the PR, not the push"* (`docs/contracts/CHANGELOG-conventions.md`) — and what
|
|
506
|
-
* cost 14.17.0 a red release PR. Both sides now read this function, so a change
|
|
507
|
-
* to the obligation cannot reach one guard and miss the other.
|
|
508
|
-
*
|
|
509
|
-
* `null` means the measurement did not run (shallow clone, missing tag). That
|
|
510
|
-
* degrades to no blocker, matching the gate's documented stance: this is a
|
|
511
|
-
* governance signal, not a correctness control, and an environment fact must
|
|
512
|
-
* not block a release.
|
|
513
|
-
*/
|
|
514
|
-
export function mix_response_blockers(
|
|
515
|
-
sectionBody: string,
|
|
516
|
-
version: string,
|
|
517
|
-
where = '`main`',
|
|
518
|
-
mix: MixObligation | null = null,
|
|
519
|
-
): string[] {
|
|
520
|
-
if (!mix || !mix.triggered) {
|
|
521
|
-
return [];
|
|
522
|
-
}
|
|
523
|
-
const idx = sectionBody.indexOf(MIX_RESPONSE_MARKER);
|
|
524
|
-
if (idx === -1) {
|
|
525
|
-
return [
|
|
526
|
-
`the ${version} section owes a governance-versus-product response ` +
|
|
527
|
-
`(${mix.level}) and carries none. Add one \`> ${MIX_RESPONSE_MARKER}\` line ` +
|
|
528
|
-
`immediately under the curated head on ${where}, naming the next cycle's ` +
|
|
529
|
-
'consumer work or a maintainer justification.',
|
|
530
|
-
];
|
|
531
|
-
}
|
|
532
|
-
const block = mix_response_block(sectionBody, idx);
|
|
533
|
-
const left = MIX_RESPONSE_PLACEHOLDERS.filter((t) => block.includes(t));
|
|
534
|
-
if (left.length > 0) {
|
|
535
|
-
return [
|
|
536
|
-
`the ${version} governance-versus-product response still carries the writer's ` +
|
|
537
|
-
`placeholder(s) (${left.map((t) => `\`${t}\``).join(', ')}) — the measured level ` +
|
|
538
|
-
'is the generator\'s, the answer is not, and `CHANGELOG.md` is published to npm. ' +
|
|
539
|
-
`Name the next cycle's consumer work (or the justification) on ${where} and re-run.`,
|
|
540
|
-
];
|
|
541
|
-
}
|
|
542
|
-
if (human_answer(block, mix.level).length < MIX_RESPONSE_MIN_CHARS) {
|
|
543
|
-
return [
|
|
544
|
-
`the ${version} governance-versus-product response carries no written answer ` +
|
|
545
|
-
`beyond the measured level (${mix.level}). The level is the generator's; the ` +
|
|
546
|
-
`response has to name the next cycle's consumer work or a maintainer ` +
|
|
547
|
-
`justification. Write it on ${where}, in the \`> \` block under the marker.`,
|
|
548
|
-
];
|
|
549
|
-
}
|
|
550
|
-
return [];
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* The promise the PREVIOUS release head made, or null when it made none.
|
|
555
|
-
*
|
|
556
|
-
* Scoped to that section's governance-response block, not to its whole body: a
|
|
557
|
-
* product line that happens to contain the phrase is not a promise the project
|
|
558
|
-
* made about the next cycle.
|
|
559
|
-
*/
|
|
560
|
-
export function previous_promise(changelogText: string, previousVersion: string): string | null {
|
|
561
|
-
const section = extract_changelog_section(changelogText, previousVersion);
|
|
562
|
-
if (section === null) return null;
|
|
563
|
-
const idx = section.body.indexOf(MIX_RESPONSE_MARKER);
|
|
564
|
-
if (idx === -1) return null;
|
|
565
|
-
const block = mix_response_block(section.body, idx);
|
|
566
|
-
return block.includes(PROMISE_PHRASE) ? block : null;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* The ONE predicate for "did this head answer the previous head's promise?".
|
|
571
|
-
*
|
|
572
|
-
* The defect: `_lib/release_material.ts` generates a `Next cycle ships …` line
|
|
573
|
-
* and nothing has ever read the previous one back, so an unmet promise can be
|
|
574
|
-
* restated indefinitely at zero cost — the debt is booked and never called. A
|
|
575
|
-
* promise nobody reads is not a commitment, it is a sentence.
|
|
576
|
-
*
|
|
577
|
-
* Three outcomes are accepted and one of them must be chosen, which is the
|
|
578
|
-
* whole mechanism: it does not require the promise to have been KEPT, and no
|
|
579
|
-
* ratio, deadline or count is enforced here. Refusing an unkept promise would
|
|
580
|
-
* make the honest answer the expensive one and teach an author to promise less
|
|
581
|
-
* rather than to answer truthfully.
|
|
582
|
-
*
|
|
583
|
-
* Length floor and placeholder scan reuse the governance-response discipline
|
|
584
|
-
* for the same reason it exists there: a bare marker, or the writer's own
|
|
585
|
-
* scaffolding, is not an answer a human wrote.
|
|
586
|
-
*/
|
|
587
|
-
export function promise_readback_blockers(
|
|
588
|
-
sectionBody: string,
|
|
589
|
-
version: string,
|
|
590
|
-
previousVersion: string,
|
|
591
|
-
promise: string | null,
|
|
592
|
-
where = '`main`',
|
|
593
|
-
): string[] {
|
|
594
|
-
if (promise === null) return [];
|
|
595
|
-
const idx = sectionBody.indexOf(PROMISE_READBACK_MARKER);
|
|
596
|
-
if (idx === -1) {
|
|
597
|
-
return [
|
|
598
|
-
`the ${version} section does not answer the ${previousVersion} head's ` +
|
|
599
|
-
`\`${PROMISE_PHRASE}\` promise. Add one \`> ${PROMISE_READBACK_MARKER}\` line ` +
|
|
600
|
-
`immediately under the curated head on ${where}, naming the promise and whether ` +
|
|
601
|
-
`it ${PROMISE_OUTCOMES.join(', ')} (the last one with its reason).`,
|
|
602
|
-
];
|
|
603
|
-
}
|
|
604
|
-
const block = mix_response_block(sectionBody, idx);
|
|
605
|
-
const left = MIX_RESPONSE_PLACEHOLDERS.filter((t) => block.includes(t));
|
|
606
|
-
if (left.length > 0) {
|
|
607
|
-
return [
|
|
608
|
-
`the ${version} previous-cycle answer still carries the writer's placeholder(s) ` +
|
|
609
|
-
`(${left.map((t) => `\`${t}\``).join(', ')}). \`CHANGELOG.md\` is published to npm. ` +
|
|
610
|
-
`Write the outcome on ${where} and re-run.`,
|
|
611
|
-
];
|
|
612
|
-
}
|
|
613
|
-
const lower = block.toLowerCase();
|
|
614
|
-
const named = PROMISE_OUTCOMES.filter((o) => lower.includes(o));
|
|
615
|
-
if (named.length === 0) {
|
|
616
|
-
return [
|
|
617
|
-
`the ${version} previous-cycle answer names no outcome. It has to say which of ` +
|
|
618
|
-
`${PROMISE_OUTCOMES.join(', ')} applies to the ${previousVersion} promise — an ` +
|
|
619
|
-
'answer that restates the promise without resolving it is the failure this reads ' +
|
|
620
|
-
'back for.',
|
|
621
|
-
];
|
|
622
|
-
}
|
|
623
|
-
if (readback_answer(block).length < MIX_RESPONSE_MIN_CHARS) {
|
|
624
|
-
return [
|
|
625
|
-
`the ${version} previous-cycle answer is shorter than a written answer ` +
|
|
626
|
-
`(${String(MIX_RESPONSE_MIN_CHARS)} characters after the marker). Name what was ` +
|
|
627
|
-
`promised and what became of it on ${where}, not the outcome word alone.`,
|
|
628
|
-
];
|
|
629
|
-
}
|
|
630
|
-
return [];
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/** What the author wrote under the read-back marker, marker and quoting removed. */
|
|
634
|
-
export function readback_answer(block: string): string {
|
|
635
|
-
return block
|
|
636
|
-
.replace(PROMISE_READBACK_MARKER, '')
|
|
637
|
-
.replace(/^[>\s.]+|[>\s.]+$/gu, '')
|
|
638
|
-
.replace(/[>\s]+/gu, ' ')
|
|
639
|
-
.trim();
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* The whole response block: the marker's line plus every blockquote line under it.
|
|
644
|
-
*
|
|
645
|
-
* Reading only the FIRST line after the marker was wrong in both directions, and
|
|
646
|
-
* both were reproduced before this was written. It **accepted** a section whose
|
|
647
|
-
* placeholder line had simply been deleted, because the machine-written level
|
|
648
|
-
* alone is 54 characters and cleared the floor — the smuggled auto-approval the
|
|
649
|
-
* placeholder exists to prevent. And it **refused** the writer's own two-line
|
|
650
|
-
* template, where the level sits on line 1 and the answer on line 2.
|
|
651
|
-
*
|
|
652
|
-
* Scoping to the block also fixes the placeholder scan, which searched the
|
|
653
|
-
* entire section body: a legitimate changelog line containing
|
|
654
|
-
* `<roadmap or issue>` blocked the release.
|
|
655
|
-
*/
|
|
656
|
-
export function mix_response_block(sectionBody: string, markerIdx: number): string {
|
|
657
|
-
const lines = sectionBody.slice(markerIdx).split('\n');
|
|
658
|
-
const out: string[] = [lines[0] ?? ''];
|
|
659
|
-
for (const line of lines.slice(1)) {
|
|
660
|
-
if (!line.trimStart().startsWith('>')) break;
|
|
661
|
-
out.push(line);
|
|
662
|
-
}
|
|
663
|
-
return out.join('\n');
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* What is left of the response block once the generator's own contribution is
|
|
668
|
-
* removed — i.e. the part a human had to write.
|
|
669
|
-
*
|
|
670
|
-
* The measured level is stripped because the writer emitted it; a floor that
|
|
671
|
-
* counted it would be satisfied by the tool on the author's behalf, which is
|
|
672
|
-
* the whole failure this obligation guards against.
|
|
673
|
-
*/
|
|
674
|
-
export function human_answer(block: string, level: string): string {
|
|
675
|
-
return block
|
|
676
|
-
.replace(MIX_RESPONSE_MARKER, '')
|
|
677
|
-
.split(level)
|
|
678
|
-
.join('')
|
|
679
|
-
.replace(/^[>\s.]+|[>\s.]+$/gu, '')
|
|
680
|
-
.replace(/[>\s]+/gu, ' ')
|
|
681
|
-
.trim();
|
|
682
|
-
}
|
|
683
|
-
|
|
684
492
|
/** Labels whose curated value is still the generator's unedited draft. */
|
|
685
493
|
export function stale_draft_labels(
|
|
686
494
|
curated: Readonly<Record<string, string>>,
|