@event4u/agent-config 8.8.0 → 8.10.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 +139 -263
- package/README.md +5 -5
- package/dist/agent-src/commands/agent-handoff.md +39 -2
- package/dist/agent-src/commands/cost/report.md +13 -3
- package/dist/agent-src/commands/research/deep.md +8 -0
- package/dist/agent-src/commands/research/report.md +7 -0
- package/dist/agent-src/commands/roadmap/create.md +12 -0
- package/dist/agent-src/commands/roadmap/process-full.md +31 -5
- package/dist/agent-src/commands/tests/create.md +28 -6
- package/dist/agent-src/commands/work.md +33 -0
- package/dist/agent-src/commands/worktree/cleanup.md +78 -0
- package/dist/agent-src/commands/worktree/create.md +62 -0
- package/dist/agent-src/commands/worktree/status.md +63 -0
- package/dist/agent-src/commands/worktree/verify.md +70 -0
- package/dist/agent-src/commands/worktree.md +85 -0
- package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +30 -1
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +60 -31
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +37 -0
- package/dist/agent-src/contexts/execution/subagent-topologies.md +34 -0
- package/dist/agent-src/contexts/execution/verification-mechanics.md +9 -9
- package/dist/agent-src/rules/agent-authority.md +1 -1
- package/dist/agent-src/rules/communication-through-line.md +1 -1
- package/dist/agent-src/rules/delegation-policy.md +5 -0
- package/dist/agent-src/rules/design-fidelity.md +40 -0
- package/dist/agent-src/rules/direct-answers.md +1 -1
- package/dist/agent-src/rules/downstream-changes.md +1 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +65 -0
- package/dist/agent-src/rules/git-history-discipline.md +18 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +6 -1
- package/dist/agent-src/rules/minimal-safe-diff.md +58 -10
- package/dist/agent-src/rules/non-destructive-by-default.md +3 -0
- package/dist/agent-src/rules/question-not-instruction.md +76 -0
- package/dist/agent-src/rules/senior-engineering-discipline.md +1 -1
- package/dist/agent-src/rules/spreadsheet-source-quality.md +61 -0
- package/dist/agent-src/rules/think-before-action.md +1 -0
- package/dist/agent-src/rules/tool-safety.md +8 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +87 -22
- package/dist/agent-src/scripts/update_roadmap_progress.ts +2 -1
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
- package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +15 -0
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +10 -3
- package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +13 -0
- package/dist/agent-src/skills/api-testing/SKILL.md +15 -1
- package/dist/agent-src/skills/character-consistency/evals/evals.json +16 -0
- package/dist/agent-src/skills/code-review/SKILL.md +2 -0
- package/dist/agent-src/skills/command-routing/evals/evals.json +14 -0
- package/dist/agent-src/skills/command-writing/SKILL.md +53 -40
- package/dist/agent-src/skills/dashboard-design/evals/evals.json +16 -0
- package/dist/agent-src/skills/dcf-modeling/evals/domain-truth.json +29 -0
- package/dist/agent-src/skills/dcf-modeling/evals/evals.json +20 -0
- package/dist/agent-src/skills/decision-record/evals/evals.json +20 -0
- package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +1 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +49 -12
- package/dist/agent-src/skills/design-intelligence/evals/evals.json +14 -0
- package/dist/agent-src/skills/design-review/SKILL.md +35 -15
- package/dist/agent-src/skills/design-system-capture/SKILL.md +84 -24
- package/dist/agent-src/skills/design-system-capture/evals/evals.json +15 -0
- package/dist/agent-src/skills/developer-like-execution/evals/evals.json +16 -0
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +27 -0
- package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +16 -0
- package/dist/agent-src/skills/editorial-calendar/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/editorial-calendar/evals/evals.json +16 -0
- package/dist/agent-src/skills/estimate-ticket/evals/evals.json +16 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -0
- package/dist/agent-src/skills/existing-ui-audit/evals/triggers.json +47 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +36 -5
- package/dist/agent-src/skills/fe-design/evals/triggers.json +47 -0
- package/dist/agent-src/skills/forecasting/evals/domain-truth.json +29 -0
- package/dist/agent-src/skills/forecasting/evals/evals.json +20 -0
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +2 -2
- package/dist/agent-src/skills/fundraising-narrative/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/fundraising-narrative/evals/evals.json +16 -0
- package/dist/agent-src/skills/html-deck/SKILL.md +27 -0
- package/dist/agent-src/skills/iconography/SKILL.md +24 -0
- package/dist/agent-src/skills/incident-commander/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/incident-commander/evals/evals.json +20 -0
- package/dist/agent-src/skills/launch-readiness/evals/evals.json +21 -0
- package/dist/agent-src/skills/logging-monitoring/evals/evals.json +16 -0
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -0
- package/dist/agent-src/skills/mcp/SKILL.md +24 -0
- package/dist/agent-src/skills/mcp-builder/SKILL.md +9 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +55 -4
- package/dist/agent-src/skills/messaging-architecture/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/messaging-architecture/evals/evals.json +16 -0
- package/dist/agent-src/skills/perf-feedback-craft/evals/evals.json +16 -0
- package/dist/agent-src/skills/pest-testing/SKILL.md +1 -1
- package/dist/agent-src/skills/playwright-testing/SKILL.md +16 -0
- package/dist/agent-src/skills/prompt-engineering-patterns/evals/evals.json +16 -0
- package/dist/agent-src/skills/refine-prompt/evals/evals.json +16 -0
- package/dist/agent-src/skills/refine-ticket/evals/evals.json +16 -0
- package/dist/agent-src/skills/release-comms/evals/evals.json +16 -0
- package/dist/agent-src/skills/rice-prioritization/evals/evals.json +20 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +39 -5
- package/dist/agent-src/skills/runway-cognition/evals/domain-truth.json +29 -0
- package/dist/agent-src/skills/runway-cognition/evals/evals.json +20 -0
- package/dist/agent-src/skills/scenario-modeling/evals/domain-truth.json +29 -0
- package/dist/agent-src/skills/scenario-modeling/evals/evals.json +20 -0
- package/dist/agent-src/skills/secrets-management/SKILL.md +7 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +12 -3
- package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +98 -0
- package/dist/agent-src/skills/spreadsheet-authoring/evals/triggers.json +47 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +27 -33
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +12 -0
- package/dist/agent-src/skills/systematic-debugging/evals/evals.json +20 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +118 -0
- package/dist/agent-src/skills/test-case-discovery/evals/triggers.json +52 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +32 -6
- package/dist/agent-src/skills/test-driven-development/evals/evals.json +16 -0
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +34 -4
- package/dist/agent-src/skills/threat-modeling/evals/evals.json +21 -0
- package/dist/agent-src/skills/typography-system/evals/evals.json +14 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -0
- package/dist/agent-src/skills/unit-economics-modeling/evals/domain-truth.json +40 -0
- package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +24 -0
- package/dist/agent-src/skills/verify-completion-evidence/evals/evals.json +16 -0
- package/dist/agent-src/skills/vision-articulation/evals/evals.json +16 -0
- package/dist/agent-src/skills/voice-and-tone-design/evals/evals.json +16 -0
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +180 -0
- package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +52 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +36 -0
- package/dist/cli/registry.js +3 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +326 -67
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +6 -6
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +25 -14
- package/dist/discovery/trust-report.md +4 -4
- package/dist/discovery/workspaces.json +25 -12
- package/dist/install/install.mjs +136 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/claude_builtin_names.js +145 -0
- package/dist/scripts/_lib/claude_builtin_names.js.map +1 -0
- package/dist/scripts/condense.js +14 -0
- package/dist/scripts/condense.js.map +1 -1
- package/docs/CLAIMS.md +36 -8
- package/docs/architecture.md +4 -4
- package/docs/archive/CHANGELOG-pre-8.9.0.md +306 -0
- package/docs/benchmark.md +113 -0
- package/docs/catalog.md +32 -8
- package/docs/command-flows.md +6 -1
- package/docs/contracts/CHANGELOG-conventions.md +6 -0
- package/docs/contracts/command-clusters.md +18 -0
- package/docs/contracts/design-artifact-lifecycle.md +93 -0
- package/docs/contracts/design-artifact-verification.md +153 -0
- package/docs/contracts/injected-block-authenticity-model.md +66 -0
- package/docs/contracts/mcp-tool-inventory.md +28 -28
- package/docs/contracts/release-pr-gating.md +26 -0
- package/docs/contracts/release-sizing.md +85 -0
- package/docs/contracts/surface-agent-contracts.md +153 -0
- package/docs/decisions/ADR-061-corpus-grounding-layer.md +13 -0
- package/docs/decisions/ADR-115-command-cluster-consolidation-phase-4.md +1 -1
- package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +9 -0
- package/docs/distribution/consumer-matrix.md +96 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +27 -0
- package/docs/guidelines/agent-infra/emphasis-budget.md +45 -0
- package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +151 -0
- package/docs/guidelines/agent-infra/tool-description-as-policy.md +35 -0
- package/docs/guidelines/design-canon.md +1 -0
- package/docs/proof.md +38 -23
- package/docs/release-runbook.md +3 -0
- package/docs/second-brain-scope.md +69 -26
- package/docs/self-review-gate.md +51 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/update_roadmap_progress.ts +2 -1
- package/src/scripts/_dispatch.bash +27 -0
- package/src/scripts/_lib/claude_builtin_names.ts +148 -0
- package/src/scripts/_lib/file_slicer.ts +117 -0
- package/src/scripts/_lib/lexical_index.ts +155 -0
- package/src/scripts/_lib/orchestration_record.ts +183 -0
- package/src/scripts/_lib/orchestration_savings.ts +114 -9
- package/src/scripts/_lib/retrieval_sanitize.ts +71 -0
- package/src/scripts/_lib/security_lint.ts +1 -0
- package/src/scripts/_lib/stat_index.ts +98 -0
- package/src/scripts/benchmark.ts +122 -0
- package/src/scripts/build_proof.ts +30 -17
- package/src/scripts/check_memory.ts +2 -0
- package/src/scripts/check_quality_regression.ts +51 -0
- package/src/scripts/check_release_adjacent_health.ts +121 -0
- package/src/scripts/check_roadmap_trackable.ts +3 -2
- package/src/scripts/check_rule_invariants.ts +275 -0
- package/src/scripts/condense.ts +14 -0
- package/src/scripts/consumer_matrix.ts +353 -0
- package/src/scripts/discovery_graph.ts +0 -0
- package/src/scripts/external_sources_denylist.json +4 -1
- package/src/scripts/generate_index.ts +6 -2
- package/src/scripts/install-hooks.sh +25 -0
- package/src/scripts/install.ts +35 -2
- package/src/scripts/learning_sidecar.ts +0 -0
- package/src/scripts/lint_agent_skill_names.ts +29 -0
- package/src/scripts/lint_changelog_rollback.ts +293 -0
- package/src/scripts/lint_knowledge_scale.ts +4 -3
- package/src/scripts/lint_roadmap_complexity.ts +124 -0
- package/src/scripts/lint_versioned_cache.ts +195 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +2 -1
- package/src/scripts/mcp_server/tools.ts +24 -1
- package/src/scripts/measure_lexical_ranking.ts +173 -0
- package/src/scripts/memory_lookup.ts +0 -0
- package/src/scripts/orchestration_record.ts +156 -0
- package/src/scripts/orchestration_savings_report.ts +65 -8
- package/src/scripts/schemas/skill.schema.json +4 -0
- package/src/scripts/score_domain_truth.ts +192 -0
- package/src/scripts/second_brain_retrieval.ts +320 -0
- package/src/scripts/second_brain_run.ts +341 -0
- package/src/scripts/self_review_gate.test.ts +65 -0
- package/src/scripts/self_review_gate.ts +272 -0
- package/src/scripts/trigger_eval_grandfather.json +223 -224
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Release-adjacent workflow health tripwire — road-to-proof-under-real-conditions
|
|
4
|
+
* Phase 1. The "red for five releases without anyone noticing" detector:
|
|
5
|
+
* the MCP worker deploy failed on every release from one minor line to the
|
|
6
|
+
* next and nothing surfaced it, because post-tag workflows are nobody's PR.
|
|
7
|
+
*
|
|
8
|
+
* For each watched workflow, look at its most recent COMPLETED run on the
|
|
9
|
+
* default branch (any trigger). If that run's conclusion is not `success`
|
|
10
|
+
* and it finished more than --max-red-hours ago (default 48) with no newer
|
|
11
|
+
* successful run, the workflow counts as durably red → exit 1 and print a
|
|
12
|
+
* GitHub error annotation per offender. The calling workflow turns that
|
|
13
|
+
* into a visible failure on main.
|
|
14
|
+
*
|
|
15
|
+
* Auth: GITHUB_TOKEN (or GH_TOKEN) env. Repo: GITHUB_REPOSITORY env or
|
|
16
|
+
* --repo owner/name.
|
|
17
|
+
*
|
|
18
|
+
* Exit codes: 0 all green/absent · 1 durably-red workflow(s) · 2 usage/env.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { argv, env, exit } from 'node:process';
|
|
22
|
+
|
|
23
|
+
const WATCHED = [
|
|
24
|
+
'publish-npm.yml',
|
|
25
|
+
'deploy-mcp-worker.yml',
|
|
26
|
+
'deploy-site.yml',
|
|
27
|
+
'site.yml',
|
|
28
|
+
'consumer-matrix.yml',
|
|
29
|
+
'release.yml',
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
interface RunInfo {
|
|
33
|
+
conclusion: string | null;
|
|
34
|
+
updated_at: string;
|
|
35
|
+
html_url: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function latestCompletedRun(
|
|
39
|
+
repo: string,
|
|
40
|
+
token: string,
|
|
41
|
+
workflow: string,
|
|
42
|
+
branch: string,
|
|
43
|
+
): Promise<RunInfo | null> {
|
|
44
|
+
const url =
|
|
45
|
+
`https://api.github.com/repos/${repo}/actions/workflows/${workflow}/runs` +
|
|
46
|
+
`?status=completed&per_page=1${branch ? `&branch=${branch}` : ''}`;
|
|
47
|
+
const res = await fetch(url, {
|
|
48
|
+
headers: {
|
|
49
|
+
Authorization: `Bearer ${token}`,
|
|
50
|
+
Accept: 'application/vnd.github+json',
|
|
51
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
if (res.status === 404) return null; // workflow not present (yet)
|
|
55
|
+
if (!res.ok) throw new Error(`GitHub API ${res.status} for ${workflow}: ${await res.text()}`);
|
|
56
|
+
const body = (await res.json()) as { workflow_runs?: RunInfo[] };
|
|
57
|
+
return body.workflow_runs?.[0] ?? null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function main(): Promise<number> {
|
|
61
|
+
const args = argv.slice(2);
|
|
62
|
+
const getOpt = (flag: string): string | undefined => {
|
|
63
|
+
const i = args.indexOf(flag);
|
|
64
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
65
|
+
};
|
|
66
|
+
const token = env.GITHUB_TOKEN ?? env.GH_TOKEN ?? '';
|
|
67
|
+
const repo = getOpt('--repo') ?? env.GITHUB_REPOSITORY ?? '';
|
|
68
|
+
const maxRedHours = Number(getOpt('--max-red-hours') ?? '48');
|
|
69
|
+
if (!token || !repo) {
|
|
70
|
+
process.stderr.write('❌ release-adjacent-health: GITHUB_TOKEN and GITHUB_REPOSITORY (or --repo) required.\n');
|
|
71
|
+
return 2;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Tag/release-triggered workflows have no branch association — query
|
|
75
|
+
// without the branch filter for those; branch-scoped for PR/push ones.
|
|
76
|
+
const branchless = new Set(['publish-npm.yml', 'deploy-mcp-worker.yml', 'release.yml']);
|
|
77
|
+
let red = 0;
|
|
78
|
+
for (const wf of WATCHED) {
|
|
79
|
+
let run: RunInfo | null;
|
|
80
|
+
try {
|
|
81
|
+
run = await latestCompletedRun(repo, token, wf, branchless.has(wf) ? '' : 'main');
|
|
82
|
+
} catch (err) {
|
|
83
|
+
process.stderr.write(`⚠️ ${wf}: ${(err as Error).message}\n`);
|
|
84
|
+
continue; // transient API failure must not fake a red
|
|
85
|
+
}
|
|
86
|
+
if (!run) {
|
|
87
|
+
process.stderr.write(`· ${wf}: no completed runs (or workflow absent) — skipping\n`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (run.conclusion === 'success') {
|
|
91
|
+
process.stderr.write(`✅ ${wf}: last completed run green\n`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const ageHours = (Date.now() - Date.parse(run.updated_at)) / 3_600_000;
|
|
95
|
+
if (ageHours >= maxRedHours) {
|
|
96
|
+
process.stdout.write(
|
|
97
|
+
`::error::release-adjacent workflow durably red: ${wf} — last completed run ` +
|
|
98
|
+
`'${run.conclusion}' ${ageHours.toFixed(0)}h ago with no newer success. ${run.html_url}\n`,
|
|
99
|
+
);
|
|
100
|
+
red += 1;
|
|
101
|
+
} else {
|
|
102
|
+
process.stderr.write(
|
|
103
|
+
`⚠️ ${wf}: last run '${run.conclusion}' ${ageHours.toFixed(0)}h ago (< ${maxRedHours}h window)\n`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (red > 0) {
|
|
108
|
+
process.stderr.write(`❌ ${red} release-adjacent workflow(s) durably red.\n`);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
process.stderr.write('✅ release-adjacent workflows healthy.\n');
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
main().then(
|
|
116
|
+
(code) => exit(code),
|
|
117
|
+
(err) => {
|
|
118
|
+
process.stderr.write(`❌ release-adjacent-health crashed: ${(err as Error).stack ?? err}\n`);
|
|
119
|
+
exit(2);
|
|
120
|
+
},
|
|
121
|
+
);
|
|
@@ -37,10 +37,11 @@ const ROADMAP_ROOT = 'agents/roadmaps';
|
|
|
37
37
|
// CHECKBOX_RE = re.compile(r"^\s*[-*]\s+\[([ xX~\-])\]\s", re.MULTILINE)
|
|
38
38
|
const CHECKBOX_RE = /^[ \t]*[-*][ \t]+\[([ xX~\-])\][ \t\f\v]/m;
|
|
39
39
|
// PHASE_RE = re.compile(
|
|
40
|
-
// r"^(#{2,3})\s+Phase\s+(\d+[a-z]?|[IVX]+|[A-Z](?:\d+)?)"
|
|
40
|
+
// r"^(#{2,3})\s+Phase\s+(\d+(?:\.\d+)*[a-z]?|[IVX]+|[A-Z](?:\d+)?)"
|
|
41
41
|
// r"(?:[\s:—\-]+(.*?))?\s*$", re.MULTILINE)
|
|
42
|
+
// Numeric ids accept dotted sub-phases ("1.0", "4.1") — mirrors the dashboard's PHASE_RE.
|
|
42
43
|
const PHASE_RE =
|
|
43
|
-
/^(#{2,3})[ \t\f\v]+Phase[ \t\f\v]+(\d+[a-z]?|[IVX]+|[A-Z](?:\d+)?)(?:[\s:—\-]+(.*?))?[ \t\f\v]*$/gm;
|
|
44
|
+
/^(#{2,3})[ \t\f\v]+Phase[ \t\f\v]+(\d+(?:\.\d+)*[a-z]?|[IVX]+|[A-Z](?:\d+)?)(?:[\s:—\-]+(.*?))?[ \t\f\v]*$/gm;
|
|
44
45
|
// FRONTMATTER_RE = re.compile(r"\A---\n(.*?)\n---\s*\n", re.DOTALL)
|
|
45
46
|
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\s*\n/;
|
|
46
47
|
const DRAFT_VALUES: ReadonlySet<string> = new Set(['draft']);
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Kernel semantic-invariants checker.
|
|
4
|
+
*
|
|
5
|
+
* Counterfactual (2026-07-10): PRs #840/#844 showed MERGED on GitHub but their
|
|
6
|
+
* content vanished from main (merge commits unreachable after a history repair);
|
|
7
|
+
* re-landed as #847 (13f244c9b — non-destructive "Irreversible external action"
|
|
8
|
+
* row + "Never act while asking" clause) and #849 (2c46e7f3b — direct-answers
|
|
9
|
+
* "no duration estimates" + "never cite the rule"). This checker's invariant set
|
|
10
|
+
* covers those exact strings — it would have flagged the loss at CI time.
|
|
11
|
+
*
|
|
12
|
+
* Loads `tests/golden/invariants.json` and verifies that every invariant
|
|
13
|
+
* string (whitespace-normalised: runs of whitespace collapse to a single
|
|
14
|
+
* space) is present in BOTH the kernel rule's `src/rules/` source and its
|
|
15
|
+
* `dist/agent-src/rules/` projection. A missing string means a rule's
|
|
16
|
+
* behaviour guarantee was silently lost in a merge / condensation pass.
|
|
17
|
+
*
|
|
18
|
+
* Kernel set: docs/contracts/kernel-membership.md § 4 (9 locked rules).
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* npx tsx src/scripts/check_rule_invariants.ts [--root ROOT]
|
|
22
|
+
* npx tsx src/scripts/check_rule_invariants.ts --mutation-selftest
|
|
23
|
+
*
|
|
24
|
+
* `--mutation-selftest` picks the first invariant and verifies the check
|
|
25
|
+
* WOULD fail if that string were absent (in-memory mutation) — guards the
|
|
26
|
+
* checker itself against a silently-green normalisation bug.
|
|
27
|
+
*
|
|
28
|
+
* Exit codes: 0 = clean / selftest passed, 1 = missing invariant(s) or
|
|
29
|
+
* selftest failure, 2 = usage error, 3 = internal error.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import * as fs from 'node:fs';
|
|
33
|
+
import * as path from 'node:path';
|
|
34
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
35
|
+
|
|
36
|
+
// src/scripts/check_rule_invariants.ts → two dirs up is the repo root.
|
|
37
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
38
|
+
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
39
|
+
|
|
40
|
+
const INVARIANTS_JSON = 'tests/golden/invariants.json';
|
|
41
|
+
|
|
42
|
+
interface InvariantEntry {
|
|
43
|
+
rule: string;
|
|
44
|
+
file: string;
|
|
45
|
+
dist: string;
|
|
46
|
+
strings: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface Miss {
|
|
50
|
+
rule: string;
|
|
51
|
+
file: string;
|
|
52
|
+
invariant: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Collapse every run of whitespace (incl. newlines) to a single space. */
|
|
56
|
+
function normalize(text: string): string {
|
|
57
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function loadEntries(root: string): InvariantEntry[] {
|
|
61
|
+
const p = path.join(root, INVARIANTS_JSON);
|
|
62
|
+
const raw = fs.readFileSync(p, 'utf-8');
|
|
63
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
64
|
+
if (!Array.isArray(parsed)) {
|
|
65
|
+
throw new Error(`${INVARIANTS_JSON}: expected a top-level array`);
|
|
66
|
+
}
|
|
67
|
+
for (const entry of parsed as InvariantEntry[]) {
|
|
68
|
+
if (
|
|
69
|
+
typeof entry.rule !== 'string' ||
|
|
70
|
+
typeof entry.file !== 'string' ||
|
|
71
|
+
typeof entry.dist !== 'string' ||
|
|
72
|
+
!Array.isArray(entry.strings) ||
|
|
73
|
+
entry.strings.some((s) => typeof s !== 'string' || s.length === 0)
|
|
74
|
+
) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`${INVARIANTS_JSON}: malformed entry ${JSON.stringify(entry.rule ?? entry)}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return parsed as InvariantEntry[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Check one entry against pre-normalised file contents. */
|
|
84
|
+
function checkEntry(
|
|
85
|
+
entry: InvariantEntry,
|
|
86
|
+
normalizedByFile: ReadonlyMap<string, string>,
|
|
87
|
+
): Miss[] {
|
|
88
|
+
const misses: Miss[] = [];
|
|
89
|
+
for (const target of [entry.file, entry.dist]) {
|
|
90
|
+
const haystack = normalizedByFile.get(target);
|
|
91
|
+
if (haystack === undefined) {
|
|
92
|
+
for (const s of entry.strings) {
|
|
93
|
+
misses.push({ rule: entry.rule, file: target, invariant: s });
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
for (const s of entry.strings) {
|
|
98
|
+
if (!haystack.includes(normalize(s))) {
|
|
99
|
+
misses.push({ rule: entry.rule, file: target, invariant: s });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return misses;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function readNormalized(root: string, entries: readonly InvariantEntry[]): Map<string, string> {
|
|
107
|
+
const out = new Map<string, string>();
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
for (const target of [entry.file, entry.dist]) {
|
|
110
|
+
if (out.has(target)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const p = path.join(root, target);
|
|
114
|
+
try {
|
|
115
|
+
out.set(target, normalize(fs.readFileSync(p, 'utf-8')));
|
|
116
|
+
} catch {
|
|
117
|
+
// Leave absent — checkEntry reports every string as missing.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function scan(root: string): Miss[] {
|
|
125
|
+
const entries = loadEntries(root);
|
|
126
|
+
const normalizedByFile = readNormalized(root, entries);
|
|
127
|
+
const misses: Miss[] = [];
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
misses.push(...checkEntry(entry, normalizedByFile));
|
|
130
|
+
}
|
|
131
|
+
return misses;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function formatText(misses: Miss[], invariantCount: number): string {
|
|
135
|
+
if (misses.length === 0) {
|
|
136
|
+
return `✅ All ${invariantCount} kernel rule invariants present in src + dist.`;
|
|
137
|
+
}
|
|
138
|
+
const lines: string[] = [
|
|
139
|
+
`❌ ${misses.length} missing kernel rule invariant(s):\n`,
|
|
140
|
+
];
|
|
141
|
+
for (const m of misses) {
|
|
142
|
+
lines.push(` 🔴 ${m.rule} — ${m.file}\n missing: "${m.invariant}"`);
|
|
143
|
+
}
|
|
144
|
+
lines.push(
|
|
145
|
+
'\nA kernel rule lost a load-bearing clause — likely a merge or condensation ' +
|
|
146
|
+
'dropped it silently (see the #840/#844 incident in this file header). ' +
|
|
147
|
+
'Restore the clause or deliberately update tests/golden/invariants.json.',
|
|
148
|
+
);
|
|
149
|
+
return lines.join('\n');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Mutation self-test: remove the first invariant string from its src file's
|
|
154
|
+
* content in-memory and verify the check catches the absence.
|
|
155
|
+
*/
|
|
156
|
+
function mutationSelftest(root: string): number {
|
|
157
|
+
const entries = loadEntries(root);
|
|
158
|
+
const first = entries[0];
|
|
159
|
+
if (first === undefined || first.strings[0] === undefined) {
|
|
160
|
+
process.stderr.write('mutation-selftest: no invariants defined\n');
|
|
161
|
+
return 1;
|
|
162
|
+
}
|
|
163
|
+
const invariant = first.strings[0];
|
|
164
|
+
const p = path.join(root, first.file);
|
|
165
|
+
const original = normalize(fs.readFileSync(p, 'utf-8'));
|
|
166
|
+
const needle = normalize(invariant);
|
|
167
|
+
if (!original.includes(needle)) {
|
|
168
|
+
process.stderr.write(
|
|
169
|
+
`mutation-selftest: baseline broken — "${invariant}" not present in ${first.file}\n`,
|
|
170
|
+
);
|
|
171
|
+
return 1;
|
|
172
|
+
}
|
|
173
|
+
const mutated = original.split(needle).join('');
|
|
174
|
+
if (mutated.includes(needle)) {
|
|
175
|
+
process.stderr.write('mutation-selftest: mutation did not remove the string\n');
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
// Run the REAL check path against the mutated content: swap the src
|
|
179
|
+
// file's normalised content in-memory and assert checkEntry reports
|
|
180
|
+
// the removed invariant as missing.
|
|
181
|
+
const normalizedByFile = readNormalized(root, [first]);
|
|
182
|
+
normalizedByFile.set(first.file, mutated);
|
|
183
|
+
const misses = checkEntry(first, normalizedByFile);
|
|
184
|
+
const detected = misses.some(
|
|
185
|
+
(m) => m.file === first.file && m.invariant === invariant,
|
|
186
|
+
);
|
|
187
|
+
if (!detected) {
|
|
188
|
+
process.stderr.write(
|
|
189
|
+
`mutation-selftest: FAILED — removing "${invariant}" from ${first.file} was NOT detected\n`,
|
|
190
|
+
);
|
|
191
|
+
return 1;
|
|
192
|
+
}
|
|
193
|
+
process.stdout.write(
|
|
194
|
+
`✅ mutation-selftest passed: removing "${invariant}" from ${first.file} is detected.\n`,
|
|
195
|
+
);
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
interface ParsedArgs {
|
|
200
|
+
root: string;
|
|
201
|
+
mutationSelftest: boolean;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parse_args(argv: readonly string[]): ParsedArgs {
|
|
205
|
+
let root = ROOT;
|
|
206
|
+
let selftest = false;
|
|
207
|
+
for (let i = 0; i < argv.length; i++) {
|
|
208
|
+
const arg = argv[i]!;
|
|
209
|
+
if (arg === '--root') {
|
|
210
|
+
const v = argv[++i];
|
|
211
|
+
if (v === undefined) {
|
|
212
|
+
_argparse_error('argument --root: expected one argument');
|
|
213
|
+
}
|
|
214
|
+
root = v;
|
|
215
|
+
} else if (arg.startsWith('--root=')) {
|
|
216
|
+
root = arg.slice('--root='.length);
|
|
217
|
+
} else if (arg === '--mutation-selftest') {
|
|
218
|
+
selftest = true;
|
|
219
|
+
} else if (arg === '-h' || arg === '--help') {
|
|
220
|
+
process.stdout.write(
|
|
221
|
+
'usage: check_rule_invariants [-h] [--root ROOT] [--mutation-selftest]\n',
|
|
222
|
+
);
|
|
223
|
+
process.exit(0);
|
|
224
|
+
} else {
|
|
225
|
+
_argparse_error(`unrecognized arguments: ${arg}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return { root, mutationSelftest: selftest };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function _argparse_error(message: string): never {
|
|
232
|
+
process.stderr.write(`check_rule_invariants: error: ${message}\n`);
|
|
233
|
+
process.exit(2);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function main(): number {
|
|
237
|
+
const args = parse_args(process.argv.slice(2));
|
|
238
|
+
try {
|
|
239
|
+
if (args.mutationSelftest) {
|
|
240
|
+
return mutationSelftest(args.root);
|
|
241
|
+
}
|
|
242
|
+
const entries = loadEntries(args.root);
|
|
243
|
+
const invariantCount = entries.reduce((n, e) => n + e.strings.length, 0);
|
|
244
|
+
const misses = scan(args.root);
|
|
245
|
+
process.stdout.write(formatText(misses, invariantCount) + '\n');
|
|
246
|
+
return misses.length ? 1 : 0;
|
|
247
|
+
} catch (e) {
|
|
248
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
249
|
+
process.stderr.write(`Internal error: ${msg}\n`);
|
|
250
|
+
return 3;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function _isCliEntry(): boolean {
|
|
255
|
+
if (process.argv[1] === undefined) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
259
|
+
if (import.meta.url === argvUrl) {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
264
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
265
|
+
return here === argv;
|
|
266
|
+
} catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
272
|
+
process.exit(main());
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export { type InvariantEntry, type Miss, ROOT, INVARIANTS_JSON, normalize, scan, main };
|
package/src/scripts/condense.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
strip_source_prefix,
|
|
41
41
|
} from './_lib/agent_src.js';
|
|
42
42
|
import { build_claude_hook_matrix } from './_lib/claude_settings_hooks.js';
|
|
43
|
+
import { is_claude_builtin_name } from './_lib/claude_builtin_names.js';
|
|
43
44
|
import { project_settings_path, load_agent_settings } from './_lib/agent_settings.js';
|
|
44
45
|
import { info, success, flush_summary, resolve_level } from './_lib/script_output.js';
|
|
45
46
|
import {
|
|
@@ -1529,6 +1530,7 @@ export function generate_claude_commands(active_command_slugs: ReadonlySet<strin
|
|
|
1529
1530
|
const current_slugs = new Set<string>();
|
|
1530
1531
|
let count = 0;
|
|
1531
1532
|
let skipped = 0;
|
|
1533
|
+
let reserved = 0;
|
|
1532
1534
|
let rendered = 0;
|
|
1533
1535
|
const auto = _read_model_auto_switch() === 'auto';
|
|
1534
1536
|
for (const [source_file, slug] of iterCommands()) {
|
|
@@ -1539,6 +1541,15 @@ export function generate_claude_commands(active_command_slugs: ReadonlySet<strin
|
|
|
1539
1541
|
skipped += 1;
|
|
1540
1542
|
continue;
|
|
1541
1543
|
}
|
|
1544
|
+
// Never claim a `/name` that is a Claude Code built-in command or
|
|
1545
|
+
// bundled skill — a same-name entry shadows the built-in (e.g. a
|
|
1546
|
+
// `review` entry hides Claude Code's own /review). Nested
|
|
1547
|
+
// `/cluster:sub` slugs are hyphen-joined and cannot collide.
|
|
1548
|
+
// See src/scripts/_lib/claude_builtin_names.ts.
|
|
1549
|
+
if (is_claude_builtin_name(slug)) {
|
|
1550
|
+
reserved += 1;
|
|
1551
|
+
continue;
|
|
1552
|
+
}
|
|
1542
1553
|
current_slugs.add(slug);
|
|
1543
1554
|
|
|
1544
1555
|
const skill_dir = path.join(MODULE_STATE.CLAUDE_SKILLS_DIR, slug);
|
|
@@ -1588,6 +1599,9 @@ export function generate_claude_commands(active_command_slugs: ReadonlySet<strin
|
|
|
1588
1599
|
if (skipped) {
|
|
1589
1600
|
msg += ` (${skipped} skipped — same-name skill exists)`;
|
|
1590
1601
|
}
|
|
1602
|
+
if (reserved) {
|
|
1603
|
+
msg += ` (${reserved} withheld — Claude Code built-in name)`;
|
|
1604
|
+
}
|
|
1591
1605
|
if (removed_dirs) {
|
|
1592
1606
|
msg += ` (${removed_dirs} stale dirs removed)`;
|
|
1593
1607
|
}
|