@event4u/agent-config 9.7.0 → 9.8.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/CHANGELOG.md +144 -0
- package/README.md +5 -5
- package/dist/agent-src/commands/commit/in-chunks.md +8 -0
- package/dist/agent-src/commands/commit.md +17 -0
- package/dist/agent-src/commands/pr/create.md +4 -0
- package/dist/agent-src/contexts/override-system.md +75 -5
- package/dist/agent-src/rules/context-hygiene.md +2 -0
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +97 -0
- package/dist/agent-src/rules/domain-safety-pii.md +1 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +13 -3
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +2 -0
- package/dist/agent-src/rules/git-history-discipline.md +2 -0
- package/dist/agent-src/rules/language-and-tone.md +2 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
- package/dist/agent-src/rules/media-governance-routing.md +2 -0
- package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
- package/dist/agent-src/rules/no-roadmap-references.md +3 -0
- package/dist/agent-src/rules/non-destructive-by-default.md +20 -0
- package/dist/agent-src/rules/onboarding-gate.md +2 -0
- package/dist/agent-src/rules/output-discipline.md +2 -0
- package/dist/agent-src/rules/persona-governance.md +2 -0
- package/dist/agent-src/rules/preservation-guard.md +3 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +106 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +1 -1
- package/dist/agent-src/rules/skill-quality.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +2 -0
- package/dist/agent-src/rules/source-of-truth.md +2 -0
- package/dist/agent-src/rules/telegraph-speak.md +2 -0
- package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/verify-before-complete.md +2 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +17 -1
- package/dist/agent-src/skills/code-intelligence/SKILL.md +94 -0
- package/dist/agent-src/skills/gated-reach/SKILL.md +185 -0
- package/dist/agent-src/skills/gated-reach/evals/triggers.json +72 -0
- package/dist/agent-src/skills/image-analyser/SKILL.md +14 -12
- package/dist/agent-src/skills/image-editing/SKILL.md +1 -0
- package/dist/agent-src/skills/readme-writing/SKILL.md +6 -1
- package/dist/agent-src/skills/screenshot-hygiene/SKILL.md +171 -0
- package/dist/agent-src/skills/secrets-management/SKILL.md +44 -1
- package/dist/agent-src/skills/subagent-orchestration/prompts/adversarial-verification-council.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-and-judge.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-competitively.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-parallel.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-steps.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-worktrees.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/judge-with-debate.md +29 -0
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +4 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/github-workflows/secret-scan.yml +65 -0
- package/dist/cli/configRoot.js +112 -0
- package/dist/cli/configRoot.js.map +1 -0
- package/dist/cli/configRoot.test.js +97 -0
- package/dist/cli/configRoot.test.js.map +1 -0
- package/dist/cli/help.js +4 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +19 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +161 -44
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +13 -8
- package/dist/discovery/trust-report.md +11 -11
- package/dist/discovery/workspaces.json +11 -6
- package/dist/install/install.mjs +16 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/server/app.js +10 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/routes/ping.js +18 -0
- package/dist/server/routes/ping.js.map +1 -1
- package/dist/server/schemas/settings.js +8 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/server/serverInfo.js +6 -2
- package/dist/server/serverInfo.js.map +1 -1
- package/dist/server/token.js +11 -4
- package/dist/server/token.js.map +1 -1
- package/dist/server/writeRoot.js +15 -1
- package/dist/server/writeRoot.js.map +1 -1
- package/dist/shared/capabilities.js +33 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/capabilities.test.js +39 -0
- package/dist/shared/capabilities.test.js.map +1 -0
- package/dist/ui/assets/{index-BiEZiZ31.js → index-CrmZ34Rg.js} +16 -16
- package/dist/ui/assets/index-CrmZ34Rg.js.map +1 -0
- package/dist/ui/index.html +14 -5
- package/docs/CLAIMS.md +62 -37
- package/docs/architecture.md +2 -2
- package/docs/benchmark.md +137 -0
- package/docs/catalog.md +8 -3
- package/docs/comparison.yaml +44 -2
- package/docs/contracts/local-server-ports.md +53 -0
- package/docs/contracts/no-runtime-boundary.md +1 -1
- package/docs/contracts/subagent-boundary.md +15 -0
- package/docs/customization.md +14 -3
- package/docs/decisions/ADR-016-installer-architecture.md +2 -0
- package/docs/decisions/ADR-088-no-external-runtime-federation.md +1 -1
- package/docs/decisions/ADR-094-agent-memory-layer-removal.md +1 -1
- package/docs/decisions/ADR-109-subagent-v1-contract.md +2 -0
- package/docs/decisions/ADR-110-discipline-profile-resolution-locus.md +2 -0
- package/docs/decisions/ADR-113-ci-native-release-label-trigger.md +11 -3
- package/docs/decisions/ADR-124-embedded-engine-doctrine.md +255 -0
- package/docs/decisions/ADR-125-doc-screenshot-anonymization.md +131 -0
- package/docs/decisions/ADR-126-internet-reach-operator-tooling.md +254 -0
- package/docs/decisions/ADR-127-enforcement-claims-must-resolve.md +150 -0
- package/docs/decisions/ADR-128-executable-evidence-and-measured-escalation.md +201 -0
- package/docs/decisions/INDEX.md +21 -0
- package/docs/decisions/engine-reclassification-2026-07.md +104 -0
- package/docs/distribution/awesome-list-submission.md +1 -1
- package/docs/distribution/registries.md +36 -27
- package/docs/enforcement-by-host.md +7 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/governance-advantage.md +1 -1
- package/docs/guides/gated-platform-reads.md +221 -0
- package/docs/positioning-evidence.md +1 -1
- package/docs/proof.md +92 -16
- package/docs/threat-model.md +1 -0
- package/package.json +5 -2
- package/src/config/agent-settings.template.yml +43 -0
- package/src/config/reach-channels.yml +217 -0
- package/src/config/reach-prescriptions-intake.yml +200 -0
- package/src/scripts/_dispatch.bash +15 -0
- package/src/scripts/_lib/exec_evidence.ts +251 -0
- package/src/scripts/_lib/fixtures/secret-detector/negatives.txt +10 -0
- package/src/scripts/_lib/fixtures/secret-detector/positives.txt +11 -0
- package/src/scripts/_lib/kernel_rules.ts +46 -0
- package/src/scripts/_lib/reddit_thread_parse.ts +598 -0
- package/src/scripts/_lib/risk_paths.ts +160 -0
- package/src/scripts/_lib/secret_detector.test.ts +103 -0
- package/src/scripts/_lib/secret_detector.ts +319 -0
- package/src/scripts/_lib/tool_probe.ts +359 -0
- package/src/scripts/adr/regenerate_index.ts +81 -6
- package/src/scripts/audit_adr_coverage.ts +33 -0
- package/src/scripts/build_proof.ts +162 -0
- package/src/scripts/check_adr_frontmatter.ts +195 -0
- package/src/scripts/check_artefact_count_messaging.ts +4 -0
- package/src/scripts/check_backstop_debt.ts +200 -0
- package/src/scripts/check_claims.ts +188 -8
- package/src/scripts/check_comparison.ts +12 -0
- package/src/scripts/check_enforcement_coverage.ts +592 -0
- package/src/scripts/check_kernel_rule_bundle.ts +16 -12
- package/src/scripts/check_reach_channels.ts +407 -0
- package/src/scripts/check_reach_staleness.ts +364 -0
- package/src/scripts/check_secret_leak.test.ts +47 -0
- package/src/scripts/check_secret_leak.ts +290 -0
- package/src/scripts/code_graph/build.ts +0 -0
- package/src/scripts/code_graph/cli.ts +207 -0
- package/src/scripts/code_graph/detect.ts +134 -0
- package/src/scripts/code_graph/extract.ts +386 -0
- package/src/scripts/code_graph/loader.ts +123 -0
- package/src/scripts/code_graph/query.ts +176 -0
- package/src/scripts/code_graph/sanitize.ts +30 -0
- package/src/scripts/code_graph/types.ts +83 -0
- package/src/scripts/code_graph/validate.ts +63 -0
- package/src/scripts/external_sources_denylist.json +3 -1
- package/src/scripts/generate_subagent_floor.ts +194 -0
- package/src/scripts/hook_manifest.yaml +13 -3
- package/src/scripts/hooks/code_graph_nudge_hook.ts +184 -0
- package/src/scripts/iron_law_sha.ts +3 -12
- package/src/scripts/lint_override_kernel_guard.ts +219 -0
- package/src/scripts/measure_rule_budget.ts +3 -12
- package/src/scripts/postinstall_gui.ts +129 -0
- package/src/scripts/reach_doctor.ts +1651 -0
- package/src/scripts/release.ts +24 -3
- package/src/scripts/risk_escalation_shadow.ts +160 -0
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/reach-channels.schema.json +134 -0
- package/src/scripts/schemas/reach-doctor-payload.schema.json +220 -0
- package/src/scripts/schemas/rule.schema.json +10 -0
- package/src/scripts/skill_linter.ts +55 -0
- package/src/scripts/validate_reach_prescriptions.ts +751 -0
- package/src/server/schemas/settings.ts +16 -0
- package/src/shared/capabilities.test.ts +43 -0
- package/src/shared/capabilities.ts +75 -0
- package/src/templates/minimal/agents-overrides-readme.md +20 -0
- package/dist/ui/assets/index-BiEZiZ31.js.map +0 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Override audit — kernel-rule overrides, reported rather than policed.
|
|
4
|
+
*
|
|
5
|
+
* The obvious design for this file was a build-failing lint on any kernel-named
|
|
6
|
+
* file under `agents/overrides/rules/`. The council talked us out of it, and the
|
|
7
|
+
* argument holds: the override layer is resolved by the *agent reading the
|
|
8
|
+
* instructions*, not by a loader, so a consumer who wants a kernel rule relaxed
|
|
9
|
+
* has other routes — a persona file, host-level agent config, a line in the
|
|
10
|
+
* prompt. Failing the build on route one does not shrink that surface; it moves
|
|
11
|
+
* the override to a route with no visibility at all, while the red X reads as
|
|
12
|
+
* coverage. That is the exact "claims enforcement it does not have" defect this
|
|
13
|
+
* whole roadmap exists to remove, pointed at ourselves.
|
|
14
|
+
*
|
|
15
|
+
* So the default output is a REPORT. A consumer reading "0 kernel overrides" or
|
|
16
|
+
* "2 kernel overrides, both registered" learns something true.
|
|
17
|
+
*
|
|
18
|
+
* Two things do hard-fail, because on our own authoring surface the coverage
|
|
19
|
+
* claim IS true:
|
|
20
|
+
* 1. a `Mode: replace` override on a kernel / safety-floor rule — the class is
|
|
21
|
+
* non-replaceable, and this is mechanically decidable from the header;
|
|
22
|
+
* 2. an `extend` on one with no entry in `agents/overrides/kernel-exceptions.yml`
|
|
23
|
+
* — tightening is allowed, going unrecorded is not.
|
|
24
|
+
*
|
|
25
|
+
* Honest limit, stated in-band: an `extend` block whose prose says "ignore
|
|
26
|
+
* everything above" passes this check. No linter reads intent. The registry
|
|
27
|
+
* entry is where a human records it, and the report is what makes it reviewable.
|
|
28
|
+
*
|
|
29
|
+
* Usage:
|
|
30
|
+
* ./scripts-run src/scripts/lint_override_kernel_guard # report
|
|
31
|
+
* ./scripts-run src/scripts/lint_override_kernel_guard --json
|
|
32
|
+
* ./scripts-run src/scripts/lint_override_kernel_guard --strict # + hard-fails
|
|
33
|
+
*
|
|
34
|
+
* Exit codes: 0 clean · 1 violation (--strict) · 2 usage/env error.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import * as fs from 'node:fs';
|
|
38
|
+
import * as path from 'node:path';
|
|
39
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
40
|
+
|
|
41
|
+
import { KERNEL_RULE_IDS, is_kernel_rule } from './_lib/kernel_rules.js';
|
|
42
|
+
|
|
43
|
+
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
const REPO_ROOT = path.resolve(_HERE, '..', '..');
|
|
45
|
+
const OVERRIDES_RULES = path.join(REPO_ROOT, 'agents', 'overrides', 'rules');
|
|
46
|
+
const REGISTRY = path.join(REPO_ROOT, 'agents', 'overrides', 'kernel-exceptions.yml');
|
|
47
|
+
const RULES_DIR = path.join(REPO_ROOT, 'src', 'rules');
|
|
48
|
+
|
|
49
|
+
export interface OverrideRow {
|
|
50
|
+
rule: string;
|
|
51
|
+
file: string;
|
|
52
|
+
mode: 'extend' | 'replace' | 'unknown';
|
|
53
|
+
kernel: boolean;
|
|
54
|
+
safety_floor: boolean;
|
|
55
|
+
registered: boolean;
|
|
56
|
+
cited: boolean;
|
|
57
|
+
violations: string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** `**Mode:** \`extend\`` — the header form the override contract specifies. */
|
|
61
|
+
export function parse_mode(text: string): 'extend' | 'replace' | 'unknown' {
|
|
62
|
+
const m = /^\s*\*\*Mode:\*\*\s*`?(extend|replace)`?\s*$/im.exec(text);
|
|
63
|
+
return m ? (m[1] as 'extend' | 'replace') : 'unknown';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** `> Overrides: <rule> §<section> — <reason>` — the citation obligation. */
|
|
67
|
+
export function has_citation(text: string): boolean {
|
|
68
|
+
return /^>\s*Overrides:\s*\S+.*—.+$/im.test(text);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Rule ids carrying `tier: safety-floor`, read from the rule sources. */
|
|
72
|
+
export function safety_floor_ids(rules_dir: string): Set<string> {
|
|
73
|
+
const out = new Set<string>();
|
|
74
|
+
if (!fs.existsSync(rules_dir)) return out;
|
|
75
|
+
for (const name of fs.readdirSync(rules_dir)) {
|
|
76
|
+
if (!name.endsWith('.md')) continue;
|
|
77
|
+
const head = fs.readFileSync(path.join(rules_dir, name), 'utf-8').slice(0, 2000);
|
|
78
|
+
if (/^tier:\s*"?safety-floor"?\s*$/im.test(head)) out.add(name.replace(/\.md$/, ''));
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Rule ids with a registered exception.
|
|
85
|
+
*
|
|
86
|
+
* Narrow reader, not a YAML dependency: the registry is a fixed two-level shape
|
|
87
|
+
* and the only fact needed is which rules appear under `exceptions:`. Anything
|
|
88
|
+
* more permissive would let a malformed registry read as coverage.
|
|
89
|
+
*/
|
|
90
|
+
export function registered_rules(text: string): Set<string> {
|
|
91
|
+
const out = new Set<string>();
|
|
92
|
+
let in_exceptions = false;
|
|
93
|
+
for (const raw of text.split('\n')) {
|
|
94
|
+
const line = raw.replace(/\s+$/, '');
|
|
95
|
+
if (/^exceptions:\s*$/.test(line)) { in_exceptions = true; continue; }
|
|
96
|
+
if (!in_exceptions) continue;
|
|
97
|
+
if (/^\S/.test(line)) break;
|
|
98
|
+
const rule_name = /^\s*-?\s*rule:\s*"?([a-z0-9-]+)"?\s*$/i.exec(line)?.[1];
|
|
99
|
+
if (rule_name !== undefined) out.add(rule_name);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function audit(): OverrideRow[] {
|
|
105
|
+
if (!fs.existsSync(OVERRIDES_RULES)) return [];
|
|
106
|
+
const floor = safety_floor_ids(RULES_DIR);
|
|
107
|
+
const registered = fs.existsSync(REGISTRY)
|
|
108
|
+
? registered_rules(fs.readFileSync(REGISTRY, 'utf-8'))
|
|
109
|
+
: new Set<string>();
|
|
110
|
+
|
|
111
|
+
const rows: OverrideRow[] = [];
|
|
112
|
+
for (const name of fs.readdirSync(OVERRIDES_RULES).sort()) {
|
|
113
|
+
if (!name.endsWith('.md')) continue;
|
|
114
|
+
const rule = name.replace(/\.md$/, '');
|
|
115
|
+
const kernel = is_kernel_rule(rule);
|
|
116
|
+
const is_floor = floor.has(rule);
|
|
117
|
+
if (!kernel && !is_floor) continue; // ordinary override — not this gate's business
|
|
118
|
+
|
|
119
|
+
const text = fs.readFileSync(path.join(OVERRIDES_RULES, name), 'utf-8');
|
|
120
|
+
const mode = parse_mode(text);
|
|
121
|
+
const cited = has_citation(text);
|
|
122
|
+
const is_registered = registered.has(rule);
|
|
123
|
+
|
|
124
|
+
const violations: string[] = [];
|
|
125
|
+
if (mode === 'replace') {
|
|
126
|
+
violations.push(
|
|
127
|
+
`\`replace\` on a ${kernel ? 'kernel' : 'safety-floor'} rule — this class may be tightened, never replaced`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (mode === 'unknown') {
|
|
131
|
+
violations.push(
|
|
132
|
+
'no readable `**Mode:**` header — an override on this class must declare its mode',
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (mode === 'extend' && !is_registered) {
|
|
136
|
+
violations.push(
|
|
137
|
+
`\`extend\` on a ${kernel ? 'kernel' : 'safety-floor'} rule with no entry in agents/overrides/kernel-exceptions.yml`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (!cited) {
|
|
141
|
+
violations.push('missing `> Overrides: <rule> §<section> — <reason>` citation');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
rows.push({
|
|
145
|
+
rule,
|
|
146
|
+
file: path.relative(REPO_ROOT, path.join(OVERRIDES_RULES, name)),
|
|
147
|
+
mode,
|
|
148
|
+
kernel,
|
|
149
|
+
safety_floor: is_floor,
|
|
150
|
+
registered: is_registered,
|
|
151
|
+
cited,
|
|
152
|
+
violations,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return rows;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function main(argv: string[]): number {
|
|
159
|
+
const as_json = argv.includes('--json');
|
|
160
|
+
const strict = argv.includes('--strict');
|
|
161
|
+
const rows = audit();
|
|
162
|
+
|
|
163
|
+
if (as_json) {
|
|
164
|
+
process.stdout.write(JSON.stringify({ kernel_overrides: rows }, null, 2) + '\n');
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const bad = rows.filter((r) => r.violations.length > 0);
|
|
169
|
+
|
|
170
|
+
if (rows.length === 0) {
|
|
171
|
+
process.stdout.write(
|
|
172
|
+
`✅ override audit: 0 kernel / safety-floor overrides present ` +
|
|
173
|
+
`(${KERNEL_RULE_IDS.length} kernel rules, none overridden)\n`,
|
|
174
|
+
);
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const lines: string[] = [];
|
|
179
|
+
lines.push(
|
|
180
|
+
`override audit: ${rows.length} kernel / safety-floor override(s) · ` +
|
|
181
|
+
`${rows.filter((r) => r.registered).length} registered · ${bad.length} with findings`,
|
|
182
|
+
);
|
|
183
|
+
for (const r of rows) {
|
|
184
|
+
const tag = r.kernel ? 'kernel' : 'safety-floor';
|
|
185
|
+
lines.push(` · ${r.rule} [${tag}] mode=${r.mode} registered=${r.registered ? 'yes' : 'NO'}`);
|
|
186
|
+
for (const v of r.violations) lines.push(` ❌ ${v}`);
|
|
187
|
+
}
|
|
188
|
+
lines.push('');
|
|
189
|
+
lines.push(
|
|
190
|
+
' Limit: this audits files under agents/overrides/. A rule can also be ' +
|
|
191
|
+
'relaxed through a persona, host config, or a direct instruction — ' +
|
|
192
|
+
'those routes are not visible here and are not claimed to be covered.',
|
|
193
|
+
);
|
|
194
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
195
|
+
|
|
196
|
+
if (strict && bad.length > 0) {
|
|
197
|
+
process.stderr.write(
|
|
198
|
+
`❌ lint_override_kernel_guard: ${bad.length} override(s) violate the non-overridable class\n`,
|
|
199
|
+
);
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Main-guard (realpath-compared, mirrors the repo convention).
|
|
206
|
+
if (process.argv[1] !== undefined) {
|
|
207
|
+
try {
|
|
208
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
209
|
+
const argv1 = fs.realpathSync(path.resolve(process.argv[1]));
|
|
210
|
+
if (here === argv1) {
|
|
211
|
+
process.exit(main(process.argv.slice(2)));
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
215
|
+
if (import.meta.url === argvUrl) {
|
|
216
|
+
process.exit(main(process.argv.slice(2)));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -29,6 +29,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
29
29
|
|
|
30
30
|
import { artefact_roots } from './_lib/agent_src.js';
|
|
31
31
|
import * as token_count from './_lib/token_count.js';
|
|
32
|
+
import { KERNEL_RULE_ID_SET } from './_lib/kernel_rules.js';
|
|
32
33
|
|
|
33
34
|
const _HERE = fileURLToPath(import.meta.url);
|
|
34
35
|
// src/scripts/measure_rule_budget.ts → parents[2] is the repo root (mirrors
|
|
@@ -47,18 +48,8 @@ const PER_RULE_HARD = 2_500;
|
|
|
47
48
|
const PER_RULE_TARGET = 2_000;
|
|
48
49
|
const PER_RULE_OVERRIDE_CEILING = 4_000; // Iron-Law-override ADR ceiling.
|
|
49
50
|
|
|
50
|
-
// Locked kernel set — docs/contracts/kernel-membership.md § 4.
|
|
51
|
-
const KERNEL_RULES: ReadonlySet<string> =
|
|
52
|
-
'agent-authority',
|
|
53
|
-
'ask-when-uncertain',
|
|
54
|
-
'commit-policy',
|
|
55
|
-
'direct-answers',
|
|
56
|
-
'language-and-tone',
|
|
57
|
-
'no-cheap-questions',
|
|
58
|
-
'non-destructive-by-default',
|
|
59
|
-
'scope-control',
|
|
60
|
-
'verify-before-complete',
|
|
61
|
-
]);
|
|
51
|
+
// Locked kernel set — single-sourced in _lib/kernel_rules.ts (contract: docs/contracts/kernel-membership.md § 4).
|
|
52
|
+
const KERNEL_RULES: ReadonlySet<string> = KERNEL_RULE_ID_SET;
|
|
62
53
|
|
|
63
54
|
export interface RuleMeasure {
|
|
64
55
|
id: string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm `postinstall` entry — on a GLOBAL (re)install of this package, terminate a
|
|
3
|
+
* running local GUI server (if one is live) and start a fresh one, so the GUI a
|
|
4
|
+
* user has open picks up the just-installed version.
|
|
5
|
+
*
|
|
6
|
+
* Requested behaviour (2026-07-22): on `npm i -g @event4u/agent-config@latest`
|
|
7
|
+
* in an interactive desktop context, restart the GUI — killing any running
|
|
8
|
+
* instance first; and if none was running, still start it. In CI / headless /
|
|
9
|
+
* non-global / opted-out contexts, do nothing.
|
|
10
|
+
*
|
|
11
|
+
* Safety contract (a postinstall must never break `npm install`):
|
|
12
|
+
* - Every path exits 0. Any error is swallowed. The package.json hook also
|
|
13
|
+
* appends `|| true` and silences stderr, so a not-yet-built dist during the
|
|
14
|
+
* repo's own `npm ci` (dist/scripts/postinstall_gui.js absent) is a no-op.
|
|
15
|
+
* - The GUI is spawned DETACHED + unref'd so npm does not hang on it.
|
|
16
|
+
* - Gated to GLOBAL installs only (`npm_config_global`), so installing this
|
|
17
|
+
* package as a transitive dependency never launches a browser.
|
|
18
|
+
* - TTY is deliberately NOT required: npm does not give lifecycle scripts a
|
|
19
|
+
* TTY, so requiring one would suppress the GUI on every real terminal
|
|
20
|
+
* install. The reliable non-interactive signals are CI / headless / opt-out.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { spawn } from 'node:child_process';
|
|
24
|
+
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import { dirname, resolve } from 'node:path';
|
|
28
|
+
|
|
29
|
+
// NB: this file ships as SOURCE under `src/scripts/` (package.json `files`), so
|
|
30
|
+
// it must import ONLY node builtins — a cross-dir import into src/cli or
|
|
31
|
+
// src/server (not in the `files` whitelist) would fail the prepack-check and
|
|
32
|
+
// crash a source run with ERR_MODULE_NOT_FOUND. The two helpers below are
|
|
33
|
+
// therefore inlined from their canonical homes (kept trivially in sync):
|
|
34
|
+
// isHeadless() — mirror of src/cli/commands/uiServe.ts
|
|
35
|
+
// serverInfo — mirror of src/server/serverInfo.ts (path + pid read + clear)
|
|
36
|
+
|
|
37
|
+
/** Mirror of `src/cli/commands/uiServe.ts` isHeadless(). */
|
|
38
|
+
function isHeadless(): boolean {
|
|
39
|
+
if (process.env['SSH_CONNECTION']) return true;
|
|
40
|
+
if (process.platform === 'linux' && !process.env['DISPLAY']) return true;
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Mirror of `src/server/serverInfo.ts` — the on-disk running-server record. */
|
|
45
|
+
function serverInfoPath(): string {
|
|
46
|
+
return resolve(homedir(), '.event4u', 'agent-config', 'local-server.json');
|
|
47
|
+
}
|
|
48
|
+
function readServerPid(): number | null {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(readFileSync(serverInfoPath(), 'utf8')) as { pid?: unknown };
|
|
51
|
+
return typeof parsed.pid === 'number' ? parsed.pid : null;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function clearServerInfo(): void {
|
|
57
|
+
try {
|
|
58
|
+
rmSync(serverInfoPath(), { force: true });
|
|
59
|
+
} catch {
|
|
60
|
+
/* best-effort */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface LaunchEnv {
|
|
65
|
+
npm_config_global?: string | undefined;
|
|
66
|
+
CI?: string | undefined;
|
|
67
|
+
AGENT_CONFIG_NO_UI?: string | undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Pure gate: should the postinstall (re)start the GUI? True only for a global
|
|
72
|
+
* install on a non-CI, non-opted-out, non-headless host. `headless` is passed in
|
|
73
|
+
* (from `isHeadless()`) to keep this unit-testable without env/platform mocking.
|
|
74
|
+
*/
|
|
75
|
+
export function shouldPostinstallLaunchGui(env: LaunchEnv, headless: boolean): boolean {
|
|
76
|
+
const truthy = (v: string | undefined): boolean => v !== undefined && v.trim() !== '' && v.trim() !== '0';
|
|
77
|
+
if (!truthy(env.npm_config_global)) return false; // only global installs
|
|
78
|
+
if (truthy(env.CI)) return false; // never in CI
|
|
79
|
+
if (truthy(env.AGENT_CONFIG_NO_UI)) return false; // explicit opt-out
|
|
80
|
+
if (headless) return false; // SSH / no-DISPLAY
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Terminate a previously-recorded live server instance, if any. Best-effort. */
|
|
85
|
+
function terminatePreviousInstance(): void {
|
|
86
|
+
const pid = readServerPid();
|
|
87
|
+
if (pid === null) return;
|
|
88
|
+
try {
|
|
89
|
+
process.kill(pid, 0); // liveness probe — throws if the process is gone
|
|
90
|
+
} catch {
|
|
91
|
+
clearServerInfo(); // stale record — nothing to kill
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
process.kill(pid, 'SIGTERM');
|
|
96
|
+
} catch {
|
|
97
|
+
// already exiting / not ours — leave the record for the new instance to overwrite
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
clearServerInfo();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Spawn the config GUI detached so npm returns immediately. */
|
|
104
|
+
function launchGuiDetached(): void {
|
|
105
|
+
const here = dirname(fileURLToPath(import.meta.url)); // dist/scripts
|
|
106
|
+
const bin = resolve(here, '..', 'cli', 'agent-config.js'); // dist/cli/agent-config.js
|
|
107
|
+
if (!existsSync(bin)) return; // built artefact missing (dev npm ci before build) — no-op
|
|
108
|
+
const child = spawn(process.execPath, [bin, 'config'], {
|
|
109
|
+
detached: true,
|
|
110
|
+
stdio: 'ignore',
|
|
111
|
+
windowsHide: true,
|
|
112
|
+
});
|
|
113
|
+
child.unref();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function main(env: LaunchEnv = process.env): number {
|
|
117
|
+
try {
|
|
118
|
+
if (!shouldPostinstallLaunchGui(env, isHeadless())) return 0;
|
|
119
|
+
terminatePreviousInstance();
|
|
120
|
+
launchGuiDetached();
|
|
121
|
+
} catch {
|
|
122
|
+
// A postinstall must never fail the install.
|
|
123
|
+
}
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
128
|
+
process.exit(main());
|
|
129
|
+
}
|