@event4u/agent-config 9.28.0 → 9.30.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/.claude-plugin/marketplace.json +1 -1
- package/AGENTS.md +6 -6
- package/CHANGELOG.md +172 -5
- package/README.md +2 -1
- package/agents/templates/.ai-council.yml.example +55 -24
- package/dist/agent-src/commands/analyze/inbox.md +22 -1
- package/dist/agent-src/commands/cost/report.md +2 -2
- package/dist/agent-src/commands/explain-run.md +31 -1
- package/dist/agent-src/commands/team/adversarial.md +8 -7
- package/dist/agent-src/commands/team/delegate.md +14 -12
- package/dist/agent-src/commands/team/review.md +9 -8
- package/dist/agent-src/commands/team/status.md +9 -8
- package/dist/agent-src/commands/team.md +29 -19
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +22 -1
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +17 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +65 -1
- package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
- package/dist/agent-src/rules/delegation-policy.md +11 -7
- package/dist/agent-src/rules/preservation-guard.md +9 -0
- package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
- package/dist/agent-src/rules/token-budget-discipline.md +1 -1
- package/dist/agent-src/scripts/roadmap_gates.ts +312 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
- package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
- package/dist/agent-src/skills/premortem/SKILL.md +30 -1
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +17 -5
- package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
- package/dist/agent-src/templates/AGENTS.md +4 -4
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +23 -0
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-22GCIJ2O.js → chunk-66OVWGB5.js} +1 -1
- package/dist/cli-delegate/{chunk-7OMQCH4V.js → chunk-BTJNRUX3.js} +22 -35
- package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-DDUNNFYX.js} +24 -17
- package/dist/cli-delegate/{chunk-PQSAQKHW.js → chunk-EQY4FLJB.js} +3 -3
- package/dist/cli-delegate/{chunk-2BE5I2MN.js → chunk-IYTGIG2Y.js} +155 -91
- package/dist/cli-delegate/{chunk-GB2FSUEE.js → chunk-LCFJIQ4J.js} +32 -0
- package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
- package/dist/cli-delegate/chunk-MMJL4TIX.js +18 -0
- package/dist/cli-delegate/{chunk-JXARCGVN.js → chunk-MP3JT3IU.js} +1 -1
- package/dist/cli-delegate/chunk-O3CO5BFB.js +161 -0
- package/dist/cli-delegate/{chunk-REKCMYOU.js → chunk-PFBV6XRT.js} +36 -20
- package/dist/cli-delegate/chunk-RVGBH4XK.js +142 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +2803 -137
- package/dist/cli-delegate/cmd_doctor.js +10 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +88 -8
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +3 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +177 -0
- package/dist/cli-delegate/cmd_settings_get.js +2 -8
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +72 -23
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -2
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +39 -2
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +12 -4
- package/dist/hooks/dispatch.js +2278 -611
- package/dist/install/install.mjs +56 -17
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +4122 -699
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +54 -0
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/condense.js +147 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +12 -7
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/settingsCarveOut.js +0 -9
- package/dist/shared/settingsCarveOut.js.map +1 -1
- package/dist/shared/settingsClasses.js +11 -9
- package/dist/shared/settingsClasses.js.map +1 -1
- package/dist/ui/assets/index-B5qGYOJo.js +26 -0
- package/dist/ui/assets/index-B5qGYOJo.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +58 -12
- package/package.json +1 -1
- package/src/agent-src/scripts/roadmap_gates.ts +312 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/src/agent-src/templates/AGENTS.md +4 -4
- package/src/config/agent-settings.template.yml +88 -66
- package/src/config/discovery/packs.yml +11 -0
- package/src/config/discovery/workspaces.yml +1 -1
- package/src/config/dispatch-economy-metrics.json +61 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/hook-token-budget.json +91 -0
- package/src/config/recycle-threshold-budget.json +12 -0
- package/src/config/rule-enforcement-baseline.json +86 -0
- package/src/scripts/_cli/cmd_conformance.ts +31 -44
- package/src/scripts/_cli/cmd_doctor.ts +65 -48
- package/src/scripts/_cli/cmd_session_recycle.ts +226 -0
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/agent_settings.ts +58 -0
- package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
- package/src/scripts/_lib/auto_dispatch.ts +23 -18
- package/src/scripts/_lib/capsule_trigger.ts +124 -0
- package/src/scripts/_lib/host_capability.ts +63 -6
- package/src/scripts/_lib/judgment_ladder.ts +634 -0
- package/src/scripts/_lib/orchestration_gate.ts +8 -0
- package/src/scripts/_lib/orchestration_record.ts +78 -10
- package/src/scripts/_lib/orchestration_savings.ts +1 -1
- package/src/scripts/_lib/recycle_envelope_paths.ts +35 -0
- package/src/scripts/_lib/self_repair.ts +354 -13
- package/src/scripts/_lib/self_repair_store.ts +28 -0
- package/src/scripts/_lib/session_eol.ts +230 -0
- package/src/scripts/_lib/session_role.ts +42 -0
- package/src/scripts/_lib/subagent_capsule.ts +341 -0
- package/src/scripts/_lib/subagent_response.ts +94 -0
- package/src/scripts/_lib/subagent_spawn.ts +13 -0
- package/src/scripts/_lib/subagent_steering.ts +9 -4
- package/src/scripts/_lib/tier_budget_routing.ts +6 -1
- package/src/scripts/_lib/worker_budget.ts +37 -2
- package/src/scripts/ai_council/clients.ts +126 -55
- package/src/scripts/ai_council/config.ts +94 -6
- package/src/scripts/ai_council/handoff.ts +113 -0
- package/src/scripts/ai_council/modes.ts +19 -4
- package/src/scripts/ai_council/orchestrator.ts +105 -0
- package/src/scripts/ai_council/quorum.ts +84 -0
- package/src/scripts/ai_council/session.ts +44 -0
- package/src/scripts/ai_council/transport_resolver.ts +127 -1
- package/src/scripts/ai_team/availability.ts +110 -0
- package/src/scripts/ai_team/config.ts +34 -14
- package/src/scripts/ai_team/team_dispatch.ts +71 -29
- package/src/scripts/ask_transport.ts +221 -0
- package/src/scripts/bench_critic_protocol.ts +396 -0
- package/src/scripts/bench_hook_injection.ts +234 -0
- package/src/scripts/check_adr_frontmatter.ts +84 -40
- package/src/scripts/check_branch_freshness.ts +1 -0
- package/src/scripts/check_corpus_staleness.ts +18 -4
- package/src/scripts/check_detector_corpus.ts +219 -0
- package/src/scripts/check_gitignore_freshness.ts +42 -8
- package/src/scripts/check_hook_bundle_freshness.ts +24 -1
- package/src/scripts/check_kernel_prefix_stability.ts +1 -0
- package/src/scripts/check_standing_rule_delivery.ts +73 -32
- package/src/scripts/check_static_layer_stability.ts +154 -0
- package/src/scripts/check_suppression_hygiene.ts +48 -0
- package/src/scripts/condense.ts +162 -2
- package/src/scripts/council_cli.ts +401 -25
- package/src/scripts/dispatch_economy_report.ts +448 -0
- package/src/scripts/dispatch_r2_reviewer.ts +15 -2
- package/src/scripts/explain_run.ts +206 -4
- package/src/scripts/forensics_report.ts +333 -0
- package/src/scripts/generate_settings_reference.ts +1 -0
- package/src/scripts/handoff_context_hook.ts +112 -4
- package/src/scripts/hook_manifest.yaml +117 -10
- package/src/scripts/hooks/concern_registry.ts +8 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +106 -35
- package/src/scripts/hooks/dispatch_hook.ts +39 -2
- package/src/scripts/hooks/edit_shape_hook.ts +303 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +19 -1
- package/src/scripts/hooks/reread_guard_hook.ts +314 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +133 -14
- package/src/scripts/hooks/session_eol_hook.ts +268 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +507 -0
- package/src/scripts/install-hooks.sh +1 -1
- package/src/scripts/language_mirror_hook.ts +89 -0
- package/src/scripts/lint_decision_impact.ts +14 -1
- package/src/scripts/lint_discovery_vocabulary.ts +2 -0
- package/src/scripts/lint_hook_manifest.ts +82 -0
- package/src/scripts/lint_never_silent.ts +276 -0
- package/src/scripts/lint_no_activation_gates.ts +271 -0
- package/src/scripts/lint_readme_serial_comma.ts +1 -0
- package/src/scripts/lint_rule_enforcement_declaration.ts +347 -0
- package/src/scripts/measure_turn_end_gate.ts +194 -0
- package/src/scripts/orchestration_record.ts +12 -1
- package/src/scripts/rebuild_hook_bundle.ts +178 -0
- package/src/scripts/release.ts +74 -2
- package/src/scripts/report_conformance_funnel.ts +284 -0
- package/src/scripts/report_imperative_density.ts +1 -0
- package/src/scripts/routing_doctor.ts +118 -36
- package/src/scripts/score_skill_selection.ts +19 -5
- package/src/scripts/self_repair_cli.ts +337 -59
- package/src/scripts/session_eol_report.ts +261 -0
- package/src/scripts/team_events_spike.ts +271 -0
- package/src/scripts/team_review_gate_hook.ts +31 -9
- package/src/server/schemas/settings.ts +24 -17
- package/src/shared/settingsCarveOut.ts +0 -10
- package/src/shared/settingsClasses.ts +11 -9
- package/src/templates/compact-instructions.md +40 -0
- package/dist/ui/assets/index-DmR0mbk4.js +0 -26
- package/dist/ui/assets/index-DmR0mbk4.js.map +0 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Safe self-heal for the self-hosted hook bundle — the builder that
|
|
4
|
+
* `check_hook_bundle_freshness` deliberately is not.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS EXISTS — measured, not hypothetical.
|
|
7
|
+
*
|
|
8
|
+
* The freshness gate reds after ANY hook-source merge from main, because
|
|
9
|
+
* `dist/hooks/dispatch.js` is untracked and nothing in the local chain
|
|
10
|
+
* rebuilds it. On 2026-08-09 that turned `task release` (9.28.0) into a
|
|
11
|
+
* blocked push with a remedy the operator had to run by hand — a routine
|
|
12
|
+
* pre-push chore promoted to a release incident. The gate is right to never
|
|
13
|
+
* build (its documented hazard: esbuild overwriting the dispatcher a running
|
|
14
|
+
* hook is executing can wedge the tool loop). The hazard is in HOW a naive
|
|
15
|
+
* rebuild writes, not in rebuilding: this script builds to a sibling `.new`
|
|
16
|
+
* file, proves the result actually dispatches, and only then renames it into
|
|
17
|
+
* place — an atomic same-directory rename that a running hook holding an open
|
|
18
|
+
* file descriptor never observes mid-write.
|
|
19
|
+
*
|
|
20
|
+
* The `.new` file MUST live in `dist/hooks/` (not a temp dir): the bundle
|
|
21
|
+
* resolves `src/scripts/hook_manifest.yaml` relative to its OWN location via
|
|
22
|
+
* the `__AGENT_CONFIG_BUNDLE__` path-depth switch. Built anywhere else, every
|
|
23
|
+
* probe answers "manifest missing" with exit 0 and the smoke test proves
|
|
24
|
+
* nothing (measured 2026-08-08).
|
|
25
|
+
*
|
|
26
|
+
* The esbuild invocation is NOT duplicated here — it is read from
|
|
27
|
+
* `package.json`'s `build:hooks` script and only the `--outfile=` is
|
|
28
|
+
* rewritten, so the flag set (banner, defines, target) has exactly one home.
|
|
29
|
+
*
|
|
30
|
+
* Behaviour:
|
|
31
|
+
* no self-hosted bundle → no-op (fresh checkout / CI — nothing is running
|
|
32
|
+
* stale code, and materialising a bundle here would
|
|
33
|
+
* change what other gates see)
|
|
34
|
+
* bundle fresh → no-op
|
|
35
|
+
* bundle stale → rebuild to `.new`, probe, rename into place
|
|
36
|
+
* build or probe fails → the `.new` file is removed and the OLD bundle
|
|
37
|
+
* keeps running (old-but-working beats broken);
|
|
38
|
+
* exit 1 so the freshness gate behind this still
|
|
39
|
+
* reds and the failure is loud.
|
|
40
|
+
*
|
|
41
|
+
* Exit codes: 0 = fresh / healed / nothing to heal · 1 = heal failed.
|
|
42
|
+
*/
|
|
43
|
+
import { spawnSync } from "node:child_process";
|
|
44
|
+
import * as fs from "node:fs";
|
|
45
|
+
import * as path from "node:path";
|
|
46
|
+
import { fileURLToPath } from "node:url";
|
|
47
|
+
|
|
48
|
+
import { check } from "./check_hook_bundle_freshness.js";
|
|
49
|
+
|
|
50
|
+
// ledger-exempt: self-heal builder, not a checker — one build → probe → atomic-rename transaction whose outcome is a single aggregate heal verdict; the per-source staleness accounting lives in check_hook_bundle_freshness, which carries the ledger.
|
|
51
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
52
|
+
const REPO_ROOT = path.resolve(HERE, "..", "..");
|
|
53
|
+
const LIVE_REL = path.join("dist", "hooks", "dispatch.js");
|
|
54
|
+
const NEW_REL = path.join("dist", "hooks", "dispatch.new.js");
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Rewrite the `--outfile=` of the canonical `build:hooks` script to the
|
|
58
|
+
* `.new` sibling. Returns null when the expected outfile token is absent —
|
|
59
|
+
* a changed `package.json` must fail loudly, never build to a guessed path.
|
|
60
|
+
*/
|
|
61
|
+
export function rewriteOutfile(script: string, newOutfile: string): string | null {
|
|
62
|
+
const token = `--outfile=${LIVE_REL}`;
|
|
63
|
+
if (!script.includes(token)) return null;
|
|
64
|
+
return script.replace(token, `--outfile=${newOutfile}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Verdict on a dispatcher probe. The probe contract is pinned against the
|
|
69
|
+
* real dry-run output: exit 0 AND parseable JSON AND a non-empty `concerns`
|
|
70
|
+
* array. Exit 0 alone is NOT enough — a bundle that cannot find its manifest
|
|
71
|
+
* answers "manifest missing" with exit 0 (measured 2026-08-08), which is
|
|
72
|
+
* exactly the broken dispatcher this probe exists to keep out of the live
|
|
73
|
+
* path. Returns null on pass, a human-readable reason on failure.
|
|
74
|
+
*/
|
|
75
|
+
export function probeVerdict(exitCode: number, stdout: string): string | null {
|
|
76
|
+
if (exitCode !== 0) return `probe exited ${String(exitCode)}`;
|
|
77
|
+
let parsed: unknown;
|
|
78
|
+
try {
|
|
79
|
+
parsed = JSON.parse(stdout);
|
|
80
|
+
} catch {
|
|
81
|
+
return `probe output is not JSON: ${stdout.slice(0, 120)}`;
|
|
82
|
+
}
|
|
83
|
+
const concerns = (parsed as { concerns?: unknown }).concerns;
|
|
84
|
+
if (!Array.isArray(concerns) || concerns.length === 0) {
|
|
85
|
+
return "probe JSON carries no concerns — the bundle dispatched nothing";
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cleanup(newAbs: string): void {
|
|
91
|
+
try {
|
|
92
|
+
fs.rmSync(newAbs, { force: true });
|
|
93
|
+
} catch {
|
|
94
|
+
// Best-effort: a leftover .new file is inert (nothing executes it).
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function main(): number {
|
|
99
|
+
const r = check(REPO_ROOT);
|
|
100
|
+
|
|
101
|
+
if (r.skipped) {
|
|
102
|
+
process.stdout.write(
|
|
103
|
+
`✅ OK rebuild_hook_bundle: no self-hosted ${LIVE_REL} — nothing runs stale code, nothing to heal\n`,
|
|
104
|
+
);
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
if (r.stale.length === 0) {
|
|
108
|
+
process.stdout.write("✅ OK rebuild_hook_bundle: bundle already fresh — no rebuild needed\n");
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
process.stdout.write(
|
|
113
|
+
`🔧 hook bundle stale (${String(r.stale.length)} newer source(s)) — rebuilding safely (build .new → probe → atomic rename)\n`,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, "package.json"), "utf-8")) as {
|
|
117
|
+
scripts?: Record<string, string>;
|
|
118
|
+
};
|
|
119
|
+
const buildScript = pkg.scripts?.["build:hooks"];
|
|
120
|
+
if (buildScript === undefined) {
|
|
121
|
+
console.error("❌ rebuild_hook_bundle: package.json has no build:hooks script");
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
const cmd = rewriteOutfile(buildScript, NEW_REL);
|
|
125
|
+
if (cmd === null) {
|
|
126
|
+
console.error(
|
|
127
|
+
`❌ rebuild_hook_bundle: build:hooks no longer targets --outfile=${LIVE_REL} — ` +
|
|
128
|
+
"refusing to build to a guessed path; update this script alongside package.json",
|
|
129
|
+
);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const newAbs = path.join(REPO_ROOT, NEW_REL);
|
|
134
|
+
const build = spawnSync("sh", ["-c", cmd], {
|
|
135
|
+
cwd: REPO_ROOT,
|
|
136
|
+
encoding: "utf-8",
|
|
137
|
+
env: { ...process.env, PATH: `${path.join(REPO_ROOT, "node_modules", ".bin")}:${process.env["PATH"] ?? ""}` },
|
|
138
|
+
});
|
|
139
|
+
if (build.status !== 0) {
|
|
140
|
+
cleanup(newAbs);
|
|
141
|
+
console.error(`❌ rebuild_hook_bundle: esbuild failed (${String(build.status)}):`);
|
|
142
|
+
console.error((build.stderr || build.stdout || "").trim());
|
|
143
|
+
return 1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const probe = spawnSync(
|
|
147
|
+
"node",
|
|
148
|
+
[newAbs, "--platform", "claude", "--event", "pre_tool_use", "--dry-run"],
|
|
149
|
+
{ cwd: REPO_ROOT, encoding: "utf-8", input: "{}" },
|
|
150
|
+
);
|
|
151
|
+
const verdict = probeVerdict(probe.status ?? 1, probe.stdout ?? "");
|
|
152
|
+
if (verdict !== null) {
|
|
153
|
+
cleanup(newAbs);
|
|
154
|
+
console.error(`❌ rebuild_hook_bundle: new bundle failed its probe — ${verdict}`);
|
|
155
|
+
console.error(" The old bundle stays in place (old-but-working beats broken).");
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fs.renameSync(newAbs, path.join(REPO_ROOT, LIVE_REL));
|
|
160
|
+
process.stdout.write(
|
|
161
|
+
`✅ OK rebuild_hook_bundle: rebuilt + probed + renamed into place (${String(r.checked)} bundled source(s))\n`,
|
|
162
|
+
);
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const invokedDirectly = ((): boolean => {
|
|
167
|
+
const entry = process.argv[1];
|
|
168
|
+
if (entry === undefined) return false;
|
|
169
|
+
try {
|
|
170
|
+
return fs.realpathSync(path.resolve(entry)) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
})();
|
|
175
|
+
|
|
176
|
+
if (invokedDirectly) {
|
|
177
|
+
process.exit(main());
|
|
178
|
+
}
|
package/src/scripts/release.ts
CHANGED
|
@@ -1073,6 +1073,78 @@ function _tag_exists_remote(tag: string): boolean {
|
|
|
1073
1073
|
return r.returncode === 0;
|
|
1074
1074
|
}
|
|
1075
1075
|
|
|
1076
|
+
/**
|
|
1077
|
+
* True when git rejected a tag push because the ref already exists on the
|
|
1078
|
+
* remote. Wording pinned from the real failure (2026-08-09, 9.28.0 — two
|
|
1079
|
+
* concurrent resume runs, the loser crashed):
|
|
1080
|
+
*
|
|
1081
|
+
* ! [remote rejected] 9.28.0 -> 9.28.0 (cannot lock ref 'refs/tags/9.28.0': reference already exists)
|
|
1082
|
+
*
|
|
1083
|
+
* plus the `[rejected] … (already exists)` variant git emits when the local
|
|
1084
|
+
* and remote tag objects differ.
|
|
1085
|
+
*/
|
|
1086
|
+
export function _is_tag_already_exists(stderr: string, stdout: string): boolean {
|
|
1087
|
+
const text = `${stderr}\n${stdout}`;
|
|
1088
|
+
return /\[(?:remote )?rejected\]/i.test(text) && /already exists/i.test(text);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/** Peeled commit the remote tag points at, or null when the tag is absent. */
|
|
1092
|
+
function _remote_tag_commit(tag: string): string | null {
|
|
1093
|
+
const r = run(['git', 'ls-remote', REMOTE, `refs/tags/${tag}`, `refs/tags/${tag}^{}`], {
|
|
1094
|
+
check: false,
|
|
1095
|
+
capture: true,
|
|
1096
|
+
});
|
|
1097
|
+
if (r.returncode !== 0) return null;
|
|
1098
|
+
const lines = r.stdout.trim().split('\n').filter(Boolean);
|
|
1099
|
+
if (lines.length === 0) return null;
|
|
1100
|
+
// An annotated tag lists the tag object AND the peeled `^{}` commit — the
|
|
1101
|
+
// peeled line wins so the comparison is commit-vs-commit regardless of
|
|
1102
|
+
// whether the two runs minted distinct tag objects for the same commit.
|
|
1103
|
+
const peeled = lines.find((l) => l.trimEnd().endsWith('^{}'));
|
|
1104
|
+
return (peeled ?? (lines[0] as string)).split('\t')[0] ?? null;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Push a tag, tolerating exactly ONE failure: the concurrent-release race.
|
|
1109
|
+
* `_tag_exists_remote` in step 8 is a live check, but between it and this
|
|
1110
|
+
* push a parallel `task release` run can land the same tag (measured
|
|
1111
|
+
* 2026-08-09, 9.28.0). When the remote tag already points at the same commit
|
|
1112
|
+
* as the local one, the repository IS in the desired state — continue. Any
|
|
1113
|
+
* other rejection, and a same-name tag on a DIFFERENT commit, stays fatal
|
|
1114
|
+
* with the push output as the error.
|
|
1115
|
+
*/
|
|
1116
|
+
function _push_tag(tag: string): void {
|
|
1117
|
+
const first = run(['git', 'push', REMOTE, tag], { check: false, capture: true });
|
|
1118
|
+
if (first.returncode === 0) {
|
|
1119
|
+
process.stdout.write(first.stdout);
|
|
1120
|
+
process.stderr.write(first.stderr);
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (_is_tag_already_exists(first.stderr, first.stdout)) {
|
|
1124
|
+
const remote = _remote_tag_commit(tag);
|
|
1125
|
+
const local = git(['rev-list', '-n', '1', tag], { capture: true });
|
|
1126
|
+
if (remote !== null && remote === local) {
|
|
1127
|
+
process.stdout.write(
|
|
1128
|
+
`↻ tag ${tag} already on ${REMOTE} at the same commit — a parallel run pushed it; continuing\n`,
|
|
1129
|
+
);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
process.stdout.write(first.stdout);
|
|
1133
|
+
process.stderr.write(first.stderr);
|
|
1134
|
+
die(
|
|
1135
|
+
`tag ${tag} exists on ${REMOTE} pointing at ${remote ?? '<unreadable>'} while the local tag ` +
|
|
1136
|
+
`points at ${local} — refusing to overwrite a published tag; ` +
|
|
1137
|
+
'delete the wrong one deliberately and re-run',
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
process.stdout.write(first.stdout);
|
|
1141
|
+
process.stderr.write(first.stderr);
|
|
1142
|
+
die(
|
|
1143
|
+
`push of tag ${tag} failed (exit ${first.returncode}) — ` +
|
|
1144
|
+
'the push output above is the real error (a pre-push gate, credentials, or protection)',
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1076
1148
|
/** Most recent PR (any state) with `release/X.Y.Z` as head, or null. */
|
|
1077
1149
|
function _pr_for_branch(branch: string): Record<string, unknown> | null {
|
|
1078
1150
|
const r = run(
|
|
@@ -2029,7 +2101,7 @@ function execute(
|
|
|
2029
2101
|
_step(8, total, `Tag ${plan.target} already on ${REMOTE} — skip`);
|
|
2030
2102
|
} else {
|
|
2031
2103
|
_step(8, total, `Tag ${plan.target} exists locally — push only`);
|
|
2032
|
-
|
|
2104
|
+
_push_tag(plan.target);
|
|
2033
2105
|
}
|
|
2034
2106
|
} else {
|
|
2035
2107
|
// Sequencing is load-bearing (release-truth Phase 1, council
|
|
@@ -2047,7 +2119,7 @@ function execute(
|
|
|
2047
2119
|
);
|
|
2048
2120
|
}
|
|
2049
2121
|
run(['git', 'tag', '-a', plan.target, '-m', tag_message_from_section(merged!.body, plan.target)]);
|
|
2050
|
-
|
|
2122
|
+
_push_tag(plan.target);
|
|
2051
2123
|
}
|
|
2052
2124
|
|
|
2053
2125
|
// ─── 9. GitHub Release ──────────────────────────────────────────────────
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* report_conformance_funnel.ts — delivery → activation → compliance, one view.
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ REPORT ONLY — THIS SCRIPT MUST NEVER BE WIRED INTO A CI WORKFLOW WITHOUT
|
|
6
|
+
* A MEASURED FALSE-POSITIVE RATE FIRST (locked policy; council requirement,
|
|
7
|
+
* road-to-feedback-9-29 Phase 4.2). Every source it joins is itself advisory
|
|
8
|
+
* and says so; a join of advisories does not become a gate by aggregation.
|
|
9
|
+
*
|
|
10
|
+
* Executes road-to-feedback-9-29 Phase 4.2: one report join over the existing
|
|
11
|
+
* conformance sources, so the three questions that were answered in three
|
|
12
|
+
* places read as the funnel they are:
|
|
13
|
+
*
|
|
14
|
+
* DELIVERY what rule text the carriers put in front of the model, and
|
|
15
|
+
* whether the two carriers agree —
|
|
16
|
+
* `conformance_scan.measureDelivered` (carrier census) +
|
|
17
|
+
* `report_carrier_divergence.compareCarriers` (divergence).
|
|
18
|
+
* ACTIVATION whether the shipped skills are invoked at all —
|
|
19
|
+
* `report_skill_activation.censusSkills` + `measureUsage`.
|
|
20
|
+
* COMPLIANCE whether a loaded skill's obligations were violated (SK-2) —
|
|
21
|
+
* `report_skill_obligation_violations.scanStore`.
|
|
22
|
+
*
|
|
23
|
+
* JOINS, NEVER RE-DERIVES. Every number below comes from an EXPORTED function
|
|
24
|
+
* of the source that owns it. A second copy of a classifier is the "second
|
|
25
|
+
* artefact to keep in sync" this repo forbids — it is how the 8 → 30 spread
|
|
26
|
+
* and the 303-vs-626 detector defect happened, and both are cited in the
|
|
27
|
+
* sources this file imports.
|
|
28
|
+
*
|
|
29
|
+
* Each axis also carries the honest-scope statement its source already
|
|
30
|
+
* carries (SK-2's mechanisable-subset ratio, activation's catalogue-not-
|
|
31
|
+
* persisted caveat, delivery's not-per-session caveat). Where a source has no
|
|
32
|
+
* local data, the axis prints its honest "no data" line rather than
|
|
33
|
+
* disappearing — an absent store is an answer, not a zero.
|
|
34
|
+
*
|
|
35
|
+
* Reached via `agent-config conformance --funnel` (no new top-level verb —
|
|
36
|
+
* the flag hangs off the existing `conformance` verb per the dispatcher-verb
|
|
37
|
+
* registry trap named in the roadmap's risk table).
|
|
38
|
+
*
|
|
39
|
+
* Exit: 0 always, except a usage error (1). Deliberate — see the header.
|
|
40
|
+
*/
|
|
41
|
+
import * as fs from 'node:fs';
|
|
42
|
+
import * as os from 'node:os';
|
|
43
|
+
import * as path from 'node:path';
|
|
44
|
+
import process from 'node:process';
|
|
45
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
46
|
+
|
|
47
|
+
import {
|
|
48
|
+
measureDelivered,
|
|
49
|
+
defaultStore,
|
|
50
|
+
type DeliveredPayload,
|
|
51
|
+
} from './conformance_scan.js';
|
|
52
|
+
import {
|
|
53
|
+
compareCarriers,
|
|
54
|
+
GLOBAL_RULES,
|
|
55
|
+
PROJECT_RULES,
|
|
56
|
+
PROJECTION_SOURCE,
|
|
57
|
+
type CarrierDivergence,
|
|
58
|
+
} from './report_carrier_divergence.js';
|
|
59
|
+
import {
|
|
60
|
+
censusSkills,
|
|
61
|
+
measureUsage,
|
|
62
|
+
SKILLS_ROOT,
|
|
63
|
+
type SkillCensus,
|
|
64
|
+
type UsageReport,
|
|
65
|
+
} from './report_skill_activation.js';
|
|
66
|
+
import { scanStore as scanObligationStore, type Sk2Report } from './report_skill_obligation_violations.js';
|
|
67
|
+
|
|
68
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
69
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
70
|
+
|
|
71
|
+
export interface FunnelOptions {
|
|
72
|
+
/** The checkout whose skills and projection source are measured. */
|
|
73
|
+
repoRoot?: string;
|
|
74
|
+
/** The project rule carrier — what the host reads at project scope. */
|
|
75
|
+
projectRulesDir?: string;
|
|
76
|
+
/** The machine-global rule carrier. */
|
|
77
|
+
globalRulesDir?: string;
|
|
78
|
+
/** The projection source, read only to attribute manual-only absences. */
|
|
79
|
+
sourceDir?: string;
|
|
80
|
+
/** Transcript store for the activation and compliance axes. */
|
|
81
|
+
store?: string;
|
|
82
|
+
/** Most-recent sessions to scan. */
|
|
83
|
+
limit?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface FunnelReport {
|
|
87
|
+
/** DELIVERY — carrier census (conformance_scan) + divergence (report_carrier_divergence). */
|
|
88
|
+
delivered: DeliveredPayload;
|
|
89
|
+
divergence: CarrierDivergence;
|
|
90
|
+
/** ACTIVATION — skill census + usage (report_skill_activation). */
|
|
91
|
+
skills: SkillCensus;
|
|
92
|
+
usage: UsageReport;
|
|
93
|
+
/** `false` is the no-data branch for both transcript-fed axes. */
|
|
94
|
+
storePresent: boolean;
|
|
95
|
+
/** COMPLIANCE — SK-2 loaded-but-violated (report_skill_obligation_violations). */
|
|
96
|
+
sk2: Sk2Report;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function buildFunnel(opts: FunnelOptions = {}): FunnelReport {
|
|
100
|
+
const repoRoot = opts.repoRoot ?? REPO_ROOT;
|
|
101
|
+
const projectRulesDir = opts.projectRulesDir ?? path.join(repoRoot, PROJECT_RULES);
|
|
102
|
+
const globalRulesDir = opts.globalRulesDir ?? path.join(os.homedir(), GLOBAL_RULES);
|
|
103
|
+
const sourceDir = opts.sourceDir ?? path.join(repoRoot, PROJECTION_SOURCE);
|
|
104
|
+
const store = opts.store ?? defaultStore(repoRoot);
|
|
105
|
+
const limit = opts.limit ?? 30;
|
|
106
|
+
return {
|
|
107
|
+
delivered: measureDelivered(projectRulesDir, globalRulesDir),
|
|
108
|
+
divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
|
|
109
|
+
skills: censusSkills(path.join(repoRoot, SKILLS_ROOT)),
|
|
110
|
+
usage: measureUsage(store, limit),
|
|
111
|
+
storePresent: fs.existsSync(store),
|
|
112
|
+
sk2: scanObligationStore(repoRoot, store, limit),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function _pct(n: number, d: number): string {
|
|
117
|
+
return d === 0 ? 'n/a' : `${((100 * n) / d).toFixed(1)}%`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function _carrierLine(label: string, c: { present: boolean; files: number; tokens: number }): string {
|
|
121
|
+
return c.present
|
|
122
|
+
? ` ${label} ${String(c.files).padStart(4)} rules ${String(c.tokens).padStart(7)} tok`
|
|
123
|
+
: ` ${label} ABSENT — not a measured zero, this carrier does not exist here`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function render(r: FunnelReport): string {
|
|
127
|
+
const lines: string[] = [];
|
|
128
|
+
lines.push('conformance funnel — delivery → activation → compliance (report, not a gate)');
|
|
129
|
+
lines.push('');
|
|
130
|
+
|
|
131
|
+
// ── DELIVERY ───────────────────────────────────────────────────────────
|
|
132
|
+
lines.push('DELIVERY — rule text the carriers put in front of the model');
|
|
133
|
+
lines.push(_carrierLine('project', r.delivered.project));
|
|
134
|
+
lines.push(_carrierLine('global ', r.delivered.global));
|
|
135
|
+
lines.push(` union ${String(r.delivered.union_tokens).padStart(7)} tok`);
|
|
136
|
+
lines.push(' Carriers as they stand NOW — a session\'s own payload is NOT recoverable:');
|
|
137
|
+
lines.push(' the transcript records no system or tools field (conformance_scan).');
|
|
138
|
+
const d = r.divergence;
|
|
139
|
+
if (!d.globalPresent) {
|
|
140
|
+
lines.push(' Divergence: the global carrier is absent — this machine loads the project');
|
|
141
|
+
lines.push(' projection only, so there is no second copy to diverge from. A real');
|
|
142
|
+
lines.push(' answer, not a skipped check.');
|
|
143
|
+
} else if (!d.projectPresent) {
|
|
144
|
+
lines.push(' Divergence: the project rule tree does not exist in this checkout — it is');
|
|
145
|
+
lines.push(' generated and gitignored. Run `task generate-tools`, then re-run. Not');
|
|
146
|
+
lines.push(' substituted with dist/, which would answer a different question.');
|
|
147
|
+
} else {
|
|
148
|
+
lines.push(
|
|
149
|
+
` Divergence: ${d.shared} shared · ${d.bodyDiff.length} body-diff · ` +
|
|
150
|
+
`${d.provenanceOnly.length} provenance-only · ${d.projectOnly.length} project-only · ` +
|
|
151
|
+
`${d.globalOnly.length} global-only` +
|
|
152
|
+
(d.manualOnlyGlobal.length > 0 ? ` (${d.manualOnlyGlobal.length} by design, ADR-004 manual)` : ''),
|
|
153
|
+
);
|
|
154
|
+
if (d.bodyDiff.length > 0) {
|
|
155
|
+
lines.push(` BODY DIVERGENCE — both copies reach the model: ${d.bodyDiff.join(', ')}`);
|
|
156
|
+
lines.push(' Precedence: the project projection is generated from src/ at this commit');
|
|
157
|
+
lines.push(' and wins; the global install is a release snapshot.');
|
|
158
|
+
}
|
|
159
|
+
if (d.unreadable.length > 0) {
|
|
160
|
+
lines.push(` Unreadable on one side (${d.unreadable.length}) — a broken install, not a disagreement.`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
lines.push('');
|
|
164
|
+
|
|
165
|
+
// ── ACTIVATION ─────────────────────────────────────────────────────────
|
|
166
|
+
lines.push('ACTIVATION — are the delivered skills invoked?');
|
|
167
|
+
lines.push(` skills shipped ${r.skills.total}`);
|
|
168
|
+
lines.push(
|
|
169
|
+
` with a machine-matchable trigger key ${r.skills.withTriggerKey.length} ` +
|
|
170
|
+
`(${_pct(r.skills.withTriggerKey.length, r.skills.total)})`,
|
|
171
|
+
);
|
|
172
|
+
if (!r.storePresent) {
|
|
173
|
+
lines.push(` NO DATA: no transcript store at ${r.usage.store} — usage is not`);
|
|
174
|
+
lines.push(' measurable here. An absent store is an answer, not a zero.');
|
|
175
|
+
} else {
|
|
176
|
+
const distinct = Object.keys(r.usage.bySkill).length;
|
|
177
|
+
lines.push(
|
|
178
|
+
` sessions=${r.usage.sessions} assistant turns=${r.usage.assistantTurns} ` +
|
|
179
|
+
`Skill invocations=${r.usage.invocations} distinct skills=${distinct} of ${r.skills.total} ` +
|
|
180
|
+
`(${_pct(distinct, r.skills.total)})`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (r.skills.withTriggerKey.length === 0) {
|
|
184
|
+
lines.push(' Honest scope: activation is NOT measurable as a rate — no skill declares');
|
|
185
|
+
lines.push(' a machine-matchable trigger, and matching description prose is the class');
|
|
186
|
+
lines.push(' this suite excludes (report_skill_activation).');
|
|
187
|
+
}
|
|
188
|
+
lines.push(' NOT MEASURED: whether each skill reached the model WITH its description —');
|
|
189
|
+
lines.push(' the host\'s injected catalogue is not persisted in the transcript, so the');
|
|
190
|
+
lines.push(' bare-name (truncation) hypothesis stays a single-session observation.');
|
|
191
|
+
lines.push('');
|
|
192
|
+
|
|
193
|
+
// ── COMPLIANCE ─────────────────────────────────────────────────────────
|
|
194
|
+
const c = r.sk2.census;
|
|
195
|
+
lines.push('COMPLIANCE — SK-2 loaded-but-violated (coverage is the headline)');
|
|
196
|
+
lines.push(` skills with a deterministic obligation ${c.skills.length}`);
|
|
197
|
+
lines.push(
|
|
198
|
+
` testable without judgement ${c.forbidden.length} artefact(s) over ` +
|
|
199
|
+
`${c.totalLines} obligation line(s)`,
|
|
200
|
+
);
|
|
201
|
+
lines.push(' The rest are deterministic in WORDING and need a reading to test — the');
|
|
202
|
+
lines.push(' FC-8 class this suite excludes, reported as uncovered, not approximated');
|
|
203
|
+
lines.push(' (report_skill_obligation_violations).');
|
|
204
|
+
if (!r.storePresent) {
|
|
205
|
+
lines.push(' NO DATA: no transcript store — loaded-but-violated cannot be observed');
|
|
206
|
+
lines.push(' here. The coverage census above is static and still holds.');
|
|
207
|
+
} else {
|
|
208
|
+
lines.push(
|
|
209
|
+
` sessions scanned=${r.sk2.sessions} with a skill in context=${r.sk2.sessionsWithASkill} ` +
|
|
210
|
+
`flags=${r.sk2.flags.length}`,
|
|
211
|
+
);
|
|
212
|
+
for (const f of r.sk2.flags) {
|
|
213
|
+
lines.push(` ${f.session} [${f.skill}] ${f.artefact} via ${f.tool}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
lines.push(' PRECISION: every flag is hand-read before any number here is cited — the');
|
|
217
|
+
lines.push(' detector cannot state its false-positive rate, so it ships detection-only.');
|
|
218
|
+
lines.push('');
|
|
219
|
+
|
|
220
|
+
lines.push('This is a REPORT. It gates on nothing and must never be wired into a CI');
|
|
221
|
+
lines.push('workflow without a measured false-positive rate first (locked policy).');
|
|
222
|
+
return lines.join('\n');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function main(argv?: readonly string[]): number {
|
|
226
|
+
const args = argv ?? process.argv.slice(2);
|
|
227
|
+
const opts: FunnelOptions = {};
|
|
228
|
+
// A value must not itself be a flag — the same hole the sibling reports
|
|
229
|
+
// close on both the flag-name and flag-value halves.
|
|
230
|
+
const value = (i: number): string | null => {
|
|
231
|
+
const v = args[i + 1];
|
|
232
|
+
return v === undefined || v.startsWith('-') ? null : v;
|
|
233
|
+
};
|
|
234
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
235
|
+
const a = args[i];
|
|
236
|
+
if (a === '--store' || a === '--project' || a === '--global' || a === '--source') {
|
|
237
|
+
const v = value(i);
|
|
238
|
+
if (v === null) {
|
|
239
|
+
process.stderr.write(`report_conformance_funnel: ${a} needs a value\n`);
|
|
240
|
+
return 1;
|
|
241
|
+
}
|
|
242
|
+
const resolved = path.resolve(v);
|
|
243
|
+
if (a === '--store') opts.store = resolved;
|
|
244
|
+
else if (a === '--project') opts.projectRulesDir = resolved;
|
|
245
|
+
else if (a === '--global') opts.globalRulesDir = resolved;
|
|
246
|
+
else opts.sourceDir = resolved;
|
|
247
|
+
i += 1;
|
|
248
|
+
} else if (a === '--limit') {
|
|
249
|
+
const v = value(i);
|
|
250
|
+
const n = v === null ? Number.NaN : Number.parseInt(v, 10);
|
|
251
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
252
|
+
process.stderr.write('report_conformance_funnel: --limit must be a positive integer\n');
|
|
253
|
+
return 1;
|
|
254
|
+
}
|
|
255
|
+
opts.limit = n;
|
|
256
|
+
i += 1;
|
|
257
|
+
} else if (a === '--help' || a === '-h') {
|
|
258
|
+
process.stdout.write(
|
|
259
|
+
'usage: report_conformance_funnel [--store PATH] [--limit N]\n' +
|
|
260
|
+
' [--project DIR] [--global DIR] [--source DIR]\n',
|
|
261
|
+
);
|
|
262
|
+
return 0;
|
|
263
|
+
} else if (a !== undefined && a.startsWith('--') && a !== '--quiet') {
|
|
264
|
+
process.stderr.write(`report_conformance_funnel: unknown flag ${a}\n`);
|
|
265
|
+
return 1;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
process.stdout.write(`${render(buildFunnel(opts))}\n`);
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function _isCliEntry(): boolean {
|
|
273
|
+
if (process.argv[1] === undefined) return false;
|
|
274
|
+
if (import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) return true;
|
|
275
|
+
try {
|
|
276
|
+
return fs.realpathSync(_HERE) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
277
|
+
} catch {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (_isCliEntry()) {
|
|
283
|
+
process.exit(main());
|
|
284
|
+
}
|
|
@@ -32,6 +32,7 @@ import * as fs from 'node:fs';
|
|
|
32
32
|
import * as path from 'node:path';
|
|
33
33
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
34
34
|
|
|
35
|
+
// ledger-exempt: advisory report that gates on nothing BY DESIGN (exit 0 always, no threshold, no baseline entry, no gate-coverage row — see the header); there is no pass/fail outcome per target to account, and acquiring one would be the promotion this report must not undergo.
|
|
35
36
|
const _HERE = fileURLToPath(import.meta.url);
|
|
36
37
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
37
38
|
|