@event4u/agent-config 14.17.0 → 14.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +207 -77
- package/README.md +72 -150
- package/dist/agent-src/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +2 -2
- package/dist/agent-src/commands/pr/merge.md +35 -44
- package/dist/agent-src/commands/review/changes.md +19 -19
- package/dist/agent-src/commands/review.md +2 -2
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +30 -3
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/git-history-discipline.md +2 -2
- package/dist/agent-src/rules/guidelines.md +1 -1
- package/dist/agent-src/rules/session-canary.md +2 -2
- package/dist/agent-src/rules/token-efficiency.md +1 -1
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +2 -0
- package/dist/agent-src/skills/adversarial-review/SKILL.md +0 -8
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +27 -1
- package/dist/agent-src/skills/canvas-design/SKILL.md +1 -1
- package/dist/agent-src/skills/code-intelligence/SKILL.md +60 -12
- package/dist/agent-src/skills/complexity-first-planning/SKILL.md +27 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +1 -1
- package/dist/agent-src/skills/design-intelligence/data/app-interface.csv +1 -1
- package/dist/agent-src/skills/design-intelligence/data/motion.csv +17 -17
- package/dist/agent-src/skills/design-intelligence/data/ux-guidelines.csv +2 -2
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +17 -0
- package/dist/agent-src/skills/design-intelligence/references/design-rules-checklist.md +4 -4
- package/dist/agent-src/skills/design-review/SKILL.md +3 -1
- package/dist/agent-src/skills/design-variations/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +1 -1
- package/dist/agent-src/skills/fe-design/SKILL.md +32 -5
- package/dist/agent-src/skills/fe-design/references/craft-floor.md +7 -0
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +47 -2
- package/dist/agent-src/skills/feature-planning/SKILL.md +34 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +37 -12
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +176 -0
- package/dist/agent-src/skills/laravel-mail/SKILL.md +44 -0
- package/dist/agent-src/skills/laravel-scheduling/SKILL.md +3 -1
- package/dist/agent-src/skills/project-analyzer/SKILL.md +0 -6
- package/dist/agent-src/skills/react-native-setup/SKILL.md +3 -3
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/sequential-thinking/SKILL.md +18 -8
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +40 -0
- package/dist/agent-src/skills/ui-apply-generic/SKILL.md +1 -1
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +1 -1
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +29 -2
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +53 -8
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +3 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/commands/recordTriggerEval.js +2 -2
- package/dist/cli/main.js +1 -1
- package/dist/cli-delegate/{chunk-AKJGUQNO.js → chunk-GZTMRYCN.js} +405 -341
- package/dist/cli-delegate/{chunk-7RXIKF62.js → chunk-IDHUQIXP.js} +283 -132
- package/dist/cli-delegate/cmd_adr_effective.js +318 -81
- package/dist/cli-delegate/cmd_conformance.js +749 -669
- package/dist/cli-delegate/cmd_doctor.js +1 -1
- package/dist/cli-delegate/cmd_export.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +1 -1
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +1 -1
- package/dist/cli-delegate/cmd_uninstall.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +39 -38
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +2764 -2439
- package/dist/install/install.mjs +588 -527
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1381 -1138
- package/dist/scripts/_lib/model_tier.js +1 -1
- package/dist/shared/settingsConsent.js +1 -1
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/docs/guidelines/design-antipatterns.md +87 -64
- package/package.json +1 -1
- package/src/agent-src/README.md +1 -1
- package/src/config/ci-local-parity.yml +6 -0
- package/src/config/conformance-claim-baseline.json +9 -0
- package/src/config/gate-coverage.yml +170 -0
- package/src/config/gate-violation-baselines.json +4 -4
- package/src/config/release-gate-locality.yml +141 -0
- package/src/scripts/_cli/cmd_doctor.ts +2 -2
- package/src/scripts/_cli/cmd_handoff.ts +1 -1
- package/src/scripts/_cli/cmd_update.ts +1 -1
- package/src/scripts/_cli/cmd_versions.ts +1 -1
- package/src/scripts/_lib/adr_frontmatter.ts +44 -0
- package/src/scripts/_lib/consequence_objects.ts +152 -0
- package/src/scripts/_lib/exec_evidence.ts +7 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +2 -2
- package/src/scripts/_lib/host_permission_checks.ts +251 -0
- package/src/scripts/_lib/model_tier.ts +1 -1
- package/src/scripts/_lib/obligation_frequency.ts +2 -2
- package/src/scripts/_lib/release_highlights.ts +265 -1
- package/src/scripts/_lib/release_material.ts +129 -0
- package/src/scripts/_lib/repeated_run.ts +77 -0
- package/src/scripts/_lib/runtime_journal.ts +26 -3
- package/src/scripts/_lib/runtime_wiring_checks.ts +12 -1
- package/src/scripts/_lib/session_register.ts +11 -6
- package/src/scripts/_lib/sqlite_schema.ts +22 -0
- package/src/scripts/_lib/surface.ts +86 -0
- package/src/scripts/_lib/tamper_vocabulary.ts +242 -0
- package/src/scripts/_lib/ui_authority.ts +116 -0
- package/src/scripts/_lib/unified_diff.ts +121 -0
- package/src/scripts/adr/regenerate_index.ts +54 -1
- package/src/scripts/adr_cite_check.ts +71 -15
- package/src/scripts/ai_council/clients.ts +1 -1
- package/src/scripts/autonomy_friction_corpus.ts +229 -0
- package/src/scripts/build_proof.ts +15 -2
- package/src/scripts/check_beta_review_markers.ts +141 -17
- package/src/scripts/check_enforcement_coverage.ts +1 -1
- package/src/scripts/check_enforcement_denominator.ts +121 -11
- package/src/scripts/check_finding_dispositions.ts +239 -6
- package/src/scripts/check_gate_coverage.ts +96 -0
- package/src/scripts/check_installed_hooks_fresh.ts +381 -0
- package/src/scripts/check_release_highlights.ts +99 -14
- package/src/scripts/check_skill_activation_claim.ts +179 -0
- package/src/scripts/check_suppression_hygiene.ts +11 -0
- package/src/scripts/check_trigger_evals.ts +1 -1
- package/src/scripts/code_graph/build.ts +230 -33
- package/src/scripts/code_graph/extract.ts +168 -33
- package/src/scripts/code_graph/types.ts +37 -2
- package/src/scripts/code_graph/validate.ts +12 -1
- package/src/scripts/conformance_scan.ts +45 -4
- package/src/scripts/detect_silent_catch.ts +403 -0
- package/src/scripts/detect_verification_tampering.ts +538 -0
- package/src/scripts/explain_run.ts +1 -1
- package/src/scripts/generate_capability_matrix.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +228 -18
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +47 -30
- package/src/scripts/hooks/augment-dispatcher.sh +1 -1
- package/src/scripts/hooks/block_no_verify.ts +3 -3
- package/src/scripts/hooks/category_a.ts +291 -0
- package/src/scripts/hooks/cline-dispatcher.sh +1 -1
- package/src/scripts/hooks/concern_registry.ts +2 -2
- package/src/scripts/hooks/cursor-dispatcher.sh +11 -5
- package/src/scripts/hooks/dispatch_hook.ts +17 -22
- package/src/scripts/hooks/evidence_independence.ts +21 -2
- package/src/scripts/hooks/{block_unauthorized_git.ts → git_command_classifier.ts} +175 -364
- package/src/scripts/hooks/host_lowering.ts +215 -0
- package/src/scripts/hooks/host_lowering.yaml +185 -0
- package/src/scripts/hooks/host_semantics.ts +105 -37
- package/src/scripts/hooks/journal_record_hook.ts +10 -0
- package/src/scripts/hooks/permission_gate.ts +117 -0
- package/src/scripts/hooks/push_settle_hook.ts +250 -0
- package/src/scripts/hooks/ship_diff_volume_hook.ts +1 -1
- package/src/scripts/hooks_status.ts +12 -10
- package/src/scripts/install-hooks.sh +134 -1
- package/src/scripts/install.ts +53 -82
- package/src/scripts/install_anthropic_key.sh +2 -2
- package/src/scripts/language_mirror_hook.ts +27 -5
- package/src/scripts/lint_design_antipattern_parity.ts +80 -0
- package/src/scripts/lint_evidence_artifacts.ts +45 -1
- package/src/scripts/lint_hook_manifest.ts +87 -3
- package/src/scripts/lint_motion_authority_drift.ts +731 -0
- package/src/scripts/lint_readme_size.ts +158 -12
- package/src/scripts/lint_reasoning_fixtures.ts +216 -0
- package/src/scripts/lint_skill_trigger_contradiction.ts +161 -0
- package/src/scripts/measure_sibling_naming.ts +150 -0
- package/src/scripts/redact_hook_capture.ts +1 -1
- package/src/scripts/release.ts +31 -143
- package/src/scripts/release_drill.ts +71 -1
- package/src/scripts/release_publication.ts +243 -3
- package/src/scripts/release_verify.ts +220 -0
- package/src/scripts/report_skill_activation.ts +319 -7
- package/src/scripts/schemas/review-findings.schema.json +6 -1
- package/src/scripts/schemas/skill.schema.json +12 -2
- package/src/scripts/self_review_gate.ts +10 -1
- package/src/scripts/session_canary_hook.ts +1 -1
- package/src/scripts/session_register_hook.ts +4 -3
- package/src/scripts/trigger_eval_grandfather.json +5 -1
- package/src/scripts/validate_agent_settings.ts +1 -1
- package/src/shared/settingsConsent.ts +1 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* The published skill-activation figure agrees with the record it came from.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS IS A SEPARATE SCRIPT FROM THE CENSUS
|
|
6
|
+
* ---------------------------------------------
|
|
7
|
+
* `report_skill_activation` is a REPORT: it gates on nothing, has no threshold,
|
|
8
|
+
* and its own docstring says it must never acquire one. This is a GATE, and the
|
|
9
|
+
* two must not share an entry point — `src/config/gate-coverage.yml` is a
|
|
10
|
+
* manifest of gates, and `_lib/gate_population.ts` classifies a script by its
|
|
11
|
+
* prefix precisely so that "is this a gate" is answered structurally rather
|
|
12
|
+
* than by whoever last edited a config. Registering a `report_*` id there forks
|
|
13
|
+
* that population, which `tests/scripts/gate_population.test.ts` refuses.
|
|
14
|
+
*
|
|
15
|
+
* So the split is not cosmetic. The report keeps its exit-0 contract; the
|
|
16
|
+
* assertion that CAN fail lives here, where a floor and a self-test belong.
|
|
17
|
+
*
|
|
18
|
+
* WHAT IT CANNOT DO, STATED SO NOBODY READS IT AS COVERED
|
|
19
|
+
* ------------------------------------------------------
|
|
20
|
+
* It compares two COMMITTED files — the census record against the sentence
|
|
21
|
+
* `docs/CLAIMS.md` publishes. It cannot tell whether the record still describes
|
|
22
|
+
* the world: the transcript store it was taken from is one machine's, keyed on
|
|
23
|
+
* the directory the census ran in, and absent from CI. Re-taking the
|
|
24
|
+
* measurement is `report_skill_activation --emit`, which refuses to write from
|
|
25
|
+
* an empty store. This gate only forbids the published sentence and the record
|
|
26
|
+
* from telling two different stories.
|
|
27
|
+
*
|
|
28
|
+
* Exit: 0 agree · 1 disagree or a file is missing.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import * as fs from 'node:fs';
|
|
32
|
+
import * as os from 'node:os';
|
|
33
|
+
import * as path from 'node:path';
|
|
34
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
35
|
+
|
|
36
|
+
import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
|
|
37
|
+
import { reportScanned } from './_lib/scan_scope.js';
|
|
38
|
+
import {
|
|
39
|
+
CLAIMS_PATH,
|
|
40
|
+
CLAIM_ID,
|
|
41
|
+
RECORD_PATH,
|
|
42
|
+
claimBlock,
|
|
43
|
+
claimProblems,
|
|
44
|
+
parseClaimFigures,
|
|
45
|
+
readRecord,
|
|
46
|
+
} from './report_skill_activation.js';
|
|
47
|
+
|
|
48
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
49
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
50
|
+
|
|
51
|
+
export function check(root: string): number {
|
|
52
|
+
const rec = readRecord(root);
|
|
53
|
+
if (rec === null) {
|
|
54
|
+
process.stderr.write(`❌ ${RECORD_PATH} is missing — the published figure has no record behind it.\n`);
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
const claimsFile = path.join(root, CLAIMS_PATH);
|
|
58
|
+
if (!fs.existsSync(claimsFile)) {
|
|
59
|
+
process.stderr.write(`❌ ${CLAIMS_PATH} is missing.\n`);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
const block = claimBlock(fs.readFileSync(claimsFile, 'utf8'), CLAIM_ID);
|
|
63
|
+
// Emitted before the verdict on both paths: a gate that reports what it
|
|
64
|
+
// inspected only when it passes leaves the coverage census blind exactly when
|
|
65
|
+
// it matters.
|
|
66
|
+
reportScanned({
|
|
67
|
+
gate: 'check_skill_activation_claim',
|
|
68
|
+
scanned: block === null ? 0 : 1,
|
|
69
|
+
units: 'published claim block(s)',
|
|
70
|
+
roots: [CLAIMS_PATH],
|
|
71
|
+
});
|
|
72
|
+
if (block === null) {
|
|
73
|
+
process.stderr.write(`❌ claim \`${CLAIM_ID}\` is not in ${CLAIMS_PATH}.\n`);
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
const problems = claimProblems(rec, parseClaimFigures(block));
|
|
77
|
+
if (problems.length > 0) {
|
|
78
|
+
process.stderr.write('❌ the published census claim and its record disagree:\n');
|
|
79
|
+
for (const p of problems) {
|
|
80
|
+
process.stderr.write(` · ${p}\n`);
|
|
81
|
+
}
|
|
82
|
+
process.stderr.write(
|
|
83
|
+
`\nRe-take the measurement (report_skill_activation --emit) and update the claim from ${RECORD_PATH}.\n`,
|
|
84
|
+
);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
process.stdout.write(`✅ census claim matches ${RECORD_PATH} (measured ${rec.measured_at}).\n`);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The gate proving it still discriminates.
|
|
93
|
+
*
|
|
94
|
+
* The third case is the one a value comparison alone would miss: a claim that
|
|
95
|
+
* silently stops stating a figure would otherwise pass every equality test it
|
|
96
|
+
* no longer participates in.
|
|
97
|
+
*/
|
|
98
|
+
function selfTest(): number {
|
|
99
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'activation-claim-selftest-'));
|
|
100
|
+
const record = {
|
|
101
|
+
schema_version: 1,
|
|
102
|
+
measured_at: '2026-01-02',
|
|
103
|
+
skills_shipped: 300,
|
|
104
|
+
with_trigger_key: 10,
|
|
105
|
+
with_trigger_corpus: 100,
|
|
106
|
+
trigger_key_and_corpus: 5,
|
|
107
|
+
human_named_only: 195,
|
|
108
|
+
sessions: 7,
|
|
109
|
+
assistant_turns: 1234,
|
|
110
|
+
invocations: 0,
|
|
111
|
+
distinct_skills_invoked: 0,
|
|
112
|
+
stores: ['fixture'],
|
|
113
|
+
};
|
|
114
|
+
const agreeing =
|
|
115
|
+
'`report_skill_activation` over 7 sessions and 1,234 assistant turns records 0 Skill ' +
|
|
116
|
+
'invocations and 0 of 300 distinct skills (measured 2026-01-02). 10 declare a ' +
|
|
117
|
+
'machine-matchable trigger key in frontmatter, 100 carry an `evals/triggers.json` corpus, ' +
|
|
118
|
+
'5 do both, and the remaining 195 are reachable only by a human naming them.';
|
|
119
|
+
const write = (name: string, body: string): string => {
|
|
120
|
+
const root = path.join(tmp, name);
|
|
121
|
+
fs.mkdirSync(path.join(root, path.dirname(RECORD_PATH)), { recursive: true });
|
|
122
|
+
fs.mkdirSync(path.join(root, path.dirname(CLAIMS_PATH)), { recursive: true });
|
|
123
|
+
fs.writeFileSync(path.join(root, RECORD_PATH), JSON.stringify(record));
|
|
124
|
+
fs.writeFileSync(
|
|
125
|
+
path.join(root, CLAIMS_PATH),
|
|
126
|
+
`# Claims\n\n### claim: ${CLAIM_ID}\n- claim: ${body}\n- kind: quant\n- status: backed\n- last_verified: 2026-01-02\n\n### claim: unrelated\n- claim: x\n`,
|
|
127
|
+
);
|
|
128
|
+
return root;
|
|
129
|
+
};
|
|
130
|
+
const run = (root: string): number =>
|
|
131
|
+
runGateCli(REPO_ROOT, 'src/scripts/check_skill_activation_claim.ts', ['--root', root], root);
|
|
132
|
+
try {
|
|
133
|
+
return runSelfTest({
|
|
134
|
+
gate: 'check_skill_activation_claim',
|
|
135
|
+
minCases: 4,
|
|
136
|
+
minRejectCases: 3,
|
|
137
|
+
cases: [
|
|
138
|
+
{
|
|
139
|
+
name: 'a turn count that moved away from the record is rejected',
|
|
140
|
+
expect: 'reject',
|
|
141
|
+
run: () => run(write('turns', agreeing.replace('1,234 assistant', '9,999 assistant'))),
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'a population redrawn to enlarge the human-named remainder is rejected',
|
|
145
|
+
expect: 'reject',
|
|
146
|
+
run: () => run(write('population', agreeing.replace('remaining 195 are', 'remaining 290 are'))),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'a claim that stops stating the population split is rejected',
|
|
150
|
+
expect: 'reject',
|
|
151
|
+
run: () => run(write('dropped', agreeing.slice(0, agreeing.indexOf('. 10 declare') + 1))),
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'a claim reproducing every recorded figure passes',
|
|
155
|
+
expect: 'accept',
|
|
156
|
+
run: () => run(write('agreeing', agreeing)),
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
} finally {
|
|
161
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function main(argv: readonly string[]): number {
|
|
166
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
167
|
+
process.stdout.write('usage: check_skill_activation_claim [--root DIR] [--self-test]\n');
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
if (argv.includes('--self-test')) {
|
|
171
|
+
return selfTest();
|
|
172
|
+
}
|
|
173
|
+
const i = argv.indexOf('--root');
|
|
174
|
+
return check(i === -1 ? REPO_ROOT : (argv[i + 1] ?? REPO_ROOT));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
178
|
+
process.exit(main(process.argv.slice(2)));
|
|
179
|
+
}
|
|
@@ -260,6 +260,17 @@ export const SUPPRESSION_INVENTORY: readonly SuppressionSpec[] = [
|
|
|
260
260
|
// mistyped path as a fresh baseline.
|
|
261
261
|
what: 'review prompts whose recorded prompt_hash does not re-derive',
|
|
262
262
|
},
|
|
263
|
+
{
|
|
264
|
+
file: 'src/config/conformance-claim-baseline.json',
|
|
265
|
+
listKey: 'undeclared',
|
|
266
|
+
tier: 'string_list',
|
|
267
|
+
// Absent at the base ref because this change introduces it. Remove the
|
|
268
|
+
// flag on the next change once the introducing commit is an ancestor of
|
|
269
|
+
// main — the scan below fails while it is set and the file resolves,
|
|
270
|
+
// so the flag closes itself rather than needing to be remembered.
|
|
271
|
+
newInThisChange: true,
|
|
272
|
+
what: 'skill and rule artefacts carrying a standard-conformance claim with no enforced_by declaration',
|
|
273
|
+
},
|
|
263
274
|
];
|
|
264
275
|
|
|
265
276
|
export interface SuppressionFinding {
|
|
@@ -150,7 +150,7 @@ function _check_one(p: string, today: PyDate): string[] {
|
|
|
150
150
|
if (age > MAX_AGE_DAYS) {
|
|
151
151
|
errors.push(
|
|
152
152
|
`${rel}: \`last_eval\` ${raw as string} is ${age}d old (> ${MAX_AGE_DAYS}d) ` +
|
|
153
|
-
'— re-run skill_trigger_eval
|
|
153
|
+
'— re-run skill_trigger_eval and bump it',
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -20,6 +20,7 @@ import { emitSqliteTwin } from './sqlite_store.js';
|
|
|
20
20
|
import {
|
|
21
21
|
type CodeEdge,
|
|
22
22
|
type CodeGraph,
|
|
23
|
+
type EdgeConfidence,
|
|
23
24
|
EXPECTED_GRAMMAR_ABI,
|
|
24
25
|
EXT_LANG,
|
|
25
26
|
type Lang,
|
|
@@ -110,6 +111,87 @@ interface SymbolTable {
|
|
|
110
111
|
classByName: Map<string, string>;
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
/**
|
|
115
|
+
* What a resolved name is allowed to BE at a given use site.
|
|
116
|
+
*
|
|
117
|
+
* The council that reviewed this repair made the same point from both seats: a
|
|
118
|
+
* confidence label cannot contain a false edge, only describe one, so the
|
|
119
|
+
* candidate set has to be filtered by capability BEFORE a confidence is
|
|
120
|
+
* assigned. A `const` cannot satisfy `foo()`; a `type` alias cannot satisfy
|
|
121
|
+
* `new T()`. Without this, adding constant/type/enum nodes in 2.1 would feed
|
|
122
|
+
* the very same-name table that produced the false `path` edge in the first
|
|
123
|
+
* place.
|
|
124
|
+
*/
|
|
125
|
+
type Capability = 'callable' | 'constructible' | 'any';
|
|
126
|
+
const CALLABLE_KINDS: ReadonlySet<string> = new Set(['function', 'method']);
|
|
127
|
+
const CONSTRUCTIBLE_KINDS: ReadonlySet<string> = new Set(['class']);
|
|
128
|
+
|
|
129
|
+
function satisfies(kind: string | undefined, want: Capability): boolean {
|
|
130
|
+
if (kind === undefined) return true; // a file id / external ref — unknown, not disqualified
|
|
131
|
+
if (want === 'callable') return CALLABLE_KINDS.has(kind);
|
|
132
|
+
if (want === 'constructible') return CONSTRUCTIBLE_KINDS.has(kind);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* One file's name bindings — the thing this engine did not have, and whose
|
|
138
|
+
* absence is the whole of defect 1.1.
|
|
139
|
+
*
|
|
140
|
+
* `locals` is every symbol DECLARED in the file. `imports` is every name the
|
|
141
|
+
* file BINDS from elsewhere, keyed on the local binding name, carrying whether
|
|
142
|
+
* the binding was resolved from a module specifier (`exact`) or merely matched
|
|
143
|
+
* by name (PHP `use`, which names a fully-qualified symbol and is matched on
|
|
144
|
+
* its base name without checking the namespace).
|
|
145
|
+
*/
|
|
146
|
+
interface FileScope {
|
|
147
|
+
locals: Map<string, string[]>;
|
|
148
|
+
imports: Map<string, { target: string; exact: boolean }>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const REL_EXTS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
|
|
152
|
+
|
|
153
|
+
/** POSIX-only join/dirname over graph ids, which are always POSIX-relative. */
|
|
154
|
+
function idDirname(id: string): string {
|
|
155
|
+
const i = id.lastIndexOf('/');
|
|
156
|
+
return i === -1 ? '' : id.slice(0, i);
|
|
157
|
+
}
|
|
158
|
+
function idJoin(dir: string, rel: string): string {
|
|
159
|
+
const parts = (dir === '' ? [] : dir.split('/')).concat(rel.split('/'));
|
|
160
|
+
const out: string[] = [];
|
|
161
|
+
for (const p of parts) {
|
|
162
|
+
if (p === '' || p === '.') continue;
|
|
163
|
+
if (p === '..') {
|
|
164
|
+
// A `..` that would escape the root is KEPT, so the caller can see
|
|
165
|
+
// the specifier points outside the indexed tree rather than
|
|
166
|
+
// silently resolving to something inside it.
|
|
167
|
+
if (out.length && out[out.length - 1] !== '..') out.pop();
|
|
168
|
+
else out.push('..');
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
out.push(p);
|
|
172
|
+
}
|
|
173
|
+
return out.join('/');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* A relative module specifier → the id of the file node it names, or `null`
|
|
178
|
+
* when that file is not in this graph (out of root, or an unsupported
|
|
179
|
+
* extension). A bare specifier (`node:path`, `js-yaml`) is external by
|
|
180
|
+
* definition and returns `null` too — the caller distinguishes the two by
|
|
181
|
+
* looking at the specifier itself.
|
|
182
|
+
*
|
|
183
|
+
* TS source imports `./types.js` and the file on disk is `types.ts`, so the
|
|
184
|
+
* runtime extension is stripped before the candidate list is tried.
|
|
185
|
+
*/
|
|
186
|
+
function resolveSpecifier(fromFile: string, spec: string, fileIds: ReadonlySet<string>): string | null {
|
|
187
|
+
if (!spec.startsWith('.')) return null;
|
|
188
|
+
const base = idJoin(idDirname(fromFile), spec);
|
|
189
|
+
const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, '');
|
|
190
|
+
const candidates = [base, ...REL_EXTS.map((e) => stem + e), ...REL_EXTS.map((e) => base + '/index' + e)];
|
|
191
|
+
for (const c of candidates) if (fileIds.has(c)) return c;
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
113
195
|
function push(map: Map<string, string[]>, k: string, v: string): void {
|
|
114
196
|
const arr = map.get(k);
|
|
115
197
|
if (arr) {
|
|
@@ -146,6 +228,11 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
146
228
|
};
|
|
147
229
|
for (const n of nodes) {
|
|
148
230
|
const lang = nodeLang.get(n.id) ?? null;
|
|
231
|
+
// `byName` stays the CALLABLE/CONSTRUCTIBLE table it always was. The
|
|
232
|
+
// kinds 2.1 added (constant / type / enum) are deliberately NOT put in
|
|
233
|
+
// it: they are reachable through a file's own scope and through
|
|
234
|
+
// `imports`, and letting them satisfy a bare `foo()` by name is the
|
|
235
|
+
// defect this roadmap removes, not a feature it adds.
|
|
149
236
|
if (n.kind === 'class' || n.kind === 'interface' || n.kind === 'trait' || n.kind === 'function') {
|
|
150
237
|
push(sym.byName, k(lang, n.label), n.id);
|
|
151
238
|
if (n.kind !== 'function' && !sym.classByName.has(k(lang, n.label)))
|
|
@@ -184,6 +271,87 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
184
271
|
return null;
|
|
185
272
|
};
|
|
186
273
|
|
|
274
|
+
const nodeKind = new Map(nodes.map((n) => [n.id, n.kind as string]));
|
|
275
|
+
const fileIds = new Set(nodes.filter((n) => n.kind === 'file').map((n) => n.id));
|
|
276
|
+
const scopes = new Map<string, FileScope>();
|
|
277
|
+
for (const ex of extracts) {
|
|
278
|
+
const locals = new Map<string, string[]>();
|
|
279
|
+
for (const n of ex.nodes) if (n.kind !== 'file') push(locals, n.label, n.id);
|
|
280
|
+
const imports = new Map<string, { target: string; exact: boolean }>();
|
|
281
|
+
for (const r of ex.rawEdges) {
|
|
282
|
+
if (r.relation !== 'imports') continue;
|
|
283
|
+
const local = r.localName ?? r.targetName;
|
|
284
|
+
if (imports.has(local)) continue; // first binding wins, deterministically
|
|
285
|
+
const spec = r.moduleSpecifier;
|
|
286
|
+
if (spec === undefined) {
|
|
287
|
+
// PHP `use A\B\C` — a fully-qualified NAME, not a module path.
|
|
288
|
+
// Resolved by base name without checking the namespace, so the
|
|
289
|
+
// binding is a lookup, not a syntactic fact: `exact: false`.
|
|
290
|
+
const hit =
|
|
291
|
+
sym.classByName.get(k(ex.lang, r.targetName)) ??
|
|
292
|
+
(sym.byName.get(k(ex.lang, r.targetName)) ?? []).find((id) => id.includes('#') && !id.includes('::'));
|
|
293
|
+
if (hit) imports.set(local, { target: hit, exact: false });
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const file = resolveSpecifier(ex.file, spec, fileIds);
|
|
297
|
+
if (file === null) {
|
|
298
|
+
// Bare (`node:path`, `zod`) or relative-but-out-of-root. Either
|
|
299
|
+
// way the module is real and NAMED, and it is not this
|
|
300
|
+
// repository's same-named symbol. `import * as path from
|
|
301
|
+
// 'node:path'` ends here, which is the whole of 1.1.
|
|
302
|
+
const named = r.importKind === 'named';
|
|
303
|
+
imports.set(local, { target: named ? `external:${spec}#${r.targetName}` : `external:${spec}`, exact: true });
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
// A namespace or default import binds the MODULE, never a
|
|
307
|
+
// same-named export inside it.
|
|
308
|
+
const member = `${file}#${r.targetName}`;
|
|
309
|
+
const target = r.importKind === 'named' && nodeKind.has(member) ? member : file;
|
|
310
|
+
imports.set(local, { target, exact: true });
|
|
311
|
+
}
|
|
312
|
+
scopes.set(ex.file, { locals, imports });
|
|
313
|
+
}
|
|
314
|
+
const emptyScope: FileScope = { locals: new Map(), imports: new Map() };
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Resolve a bare name at a use site inside `file`, capability-filtered.
|
|
318
|
+
*
|
|
319
|
+
* The ladder is the language's own: a LOCAL declaration shadows an import
|
|
320
|
+
* (both council seats named this, and JS/TS/PHP all agree), an import
|
|
321
|
+
* resolved from a specifier is next, and only then the repo-wide same-name
|
|
322
|
+
* table — which is a guess and is labelled as one.
|
|
323
|
+
*/
|
|
324
|
+
const resolveInScope = (
|
|
325
|
+
file: string,
|
|
326
|
+
name: string,
|
|
327
|
+
want: Capability,
|
|
328
|
+
lang: Lang,
|
|
329
|
+
scopeOnly = false,
|
|
330
|
+
): { target: string; confidence: EdgeConfidence } | null => {
|
|
331
|
+
const scope = scopes.get(file) ?? emptyScope;
|
|
332
|
+
for (const id of scope.locals.get(name) ?? [])
|
|
333
|
+
if (satisfies(nodeKind.get(id), want)) return { target: id, confidence: 'EXTRACTED' };
|
|
334
|
+
const bound = scope.imports.get(name);
|
|
335
|
+
if (bound && satisfies(nodeKind.get(bound.target), want))
|
|
336
|
+
return { target: bound.target, confidence: bound.exact ? 'EXTRACTED' : 'INFERRED' };
|
|
337
|
+
if (scopeOnly) return null;
|
|
338
|
+
const wide =
|
|
339
|
+
want === 'constructible'
|
|
340
|
+
? sym.classByName.get(k(lang, name))
|
|
341
|
+
: (sym.byName.get(k(lang, name)) ?? []).find(
|
|
342
|
+
(id) => id.includes('#') && !id.includes('::') && satisfies(nodeKind.get(id), want),
|
|
343
|
+
);
|
|
344
|
+
// Reached only by matching a name across the whole repository, with no
|
|
345
|
+
// binding in this file to justify it. That is not a syntactic fact and
|
|
346
|
+
// no longer claims to be one.
|
|
347
|
+
if (wide) return { target: wide, confidence: 'INFERRED' };
|
|
348
|
+
return null;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Deduped the same way emitted edges are, so the count is comparable to
|
|
352
|
+
// `edges.length` rather than being a raw call-site tally that reads as a
|
|
353
|
+
// larger loss than the graph actually took.
|
|
354
|
+
const suppressedKeys = new Set<string>();
|
|
187
355
|
const edges: CodeEdge[] = [];
|
|
188
356
|
const seen = new Set<string>();
|
|
189
357
|
const emit = (e: CodeEdge): void => {
|
|
@@ -198,8 +366,13 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
198
366
|
for (const ex of extracts) {
|
|
199
367
|
// structural inherits edges (same-language resolution)
|
|
200
368
|
for (const inh of ex.inherits) {
|
|
201
|
-
const
|
|
202
|
-
emit({
|
|
369
|
+
const hit = resolveInScope(ex.file, inh.parentName, 'constructible', ex.lang);
|
|
370
|
+
emit({
|
|
371
|
+
source: inh.classId,
|
|
372
|
+
target: hit?.target ?? `symbol:${inh.parentName}`,
|
|
373
|
+
relation: 'inherits',
|
|
374
|
+
confidence: hit?.confidence ?? 'EXTRACTED',
|
|
375
|
+
});
|
|
203
376
|
}
|
|
204
377
|
for (const r of ex.rawEdges) resolveRawEdge(r, ex);
|
|
205
378
|
}
|
|
@@ -216,50 +389,50 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
216
389
|
});
|
|
217
390
|
return;
|
|
218
391
|
}
|
|
219
|
-
if (r.relation === 'imports'
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
// which is barely better than the missing edge it replaced.
|
|
226
|
-
//
|
|
227
|
-
// Same predicate as the `free` call branch below (`#` and no `::`),
|
|
228
|
-
// so a function reference and a function call resolve identically
|
|
229
|
-
// rather than by two rules that can drift.
|
|
230
|
-
const resolved =
|
|
231
|
-
sym.classByName.get(k(ex.lang, r.targetName)) ??
|
|
232
|
-
(sym.byName.get(k(ex.lang, r.targetName)) ?? []).find(
|
|
233
|
-
(id) => id.includes('#') && !id.includes('::'),
|
|
234
|
-
);
|
|
392
|
+
if (r.relation === 'imports') {
|
|
393
|
+
// The import's own binding, computed once per file above. A
|
|
394
|
+
// specifier-resolved binding is a syntactic fact — the file says
|
|
395
|
+
// where the name comes from — and a PHP `use` matched by base name
|
|
396
|
+
// is not, and says so.
|
|
397
|
+
const bound = (scopes.get(ex.file) ?? emptyScope).imports.get(r.localName ?? r.targetName);
|
|
235
398
|
emit({
|
|
236
399
|
source: r.sourceId,
|
|
237
|
-
target:
|
|
238
|
-
relation:
|
|
239
|
-
confidence: 'EXTRACTED',
|
|
400
|
+
target: bound?.target ?? `symbol:${r.targetName}`,
|
|
401
|
+
relation: 'imports',
|
|
402
|
+
confidence: bound ? (bound.exact ? 'EXTRACTED' : 'INFERRED') : 'EXTRACTED',
|
|
240
403
|
});
|
|
241
404
|
return;
|
|
242
405
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
406
|
+
if (r.relation === 'uses') {
|
|
407
|
+
// A registry literal (`{ foo: handleFoo }`) or an object shorthand
|
|
408
|
+
// (`{ id, label }`) names something bound IN THIS FILE. It is
|
|
409
|
+
// resolved through the file's scope and NOWHERE else: a shorthand
|
|
410
|
+
// property almost always names a local variable, and matching it
|
|
411
|
+
// against a same-named declaration elsewhere in the repository is
|
|
412
|
+
// the defect this roadmap removes, applied to a second relation.
|
|
413
|
+
const hit = resolveInScope(ex.file, r.targetName, 'any', ex.lang, true);
|
|
246
414
|
emit({
|
|
247
415
|
source: r.sourceId,
|
|
248
|
-
target:
|
|
249
|
-
relation: '
|
|
250
|
-
confidence: 'EXTRACTED',
|
|
416
|
+
target: hit?.target ?? `symbol:${r.targetName}`,
|
|
417
|
+
relation: 'uses',
|
|
418
|
+
confidence: hit?.confidence ?? 'EXTRACTED',
|
|
251
419
|
});
|
|
252
420
|
return;
|
|
253
421
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
);
|
|
422
|
+
// relation === 'calls'
|
|
423
|
+
if (r.callStyle === 'new' || r.callStyle === 'free') {
|
|
424
|
+
const want: Capability = r.callStyle === 'new' ? 'constructible' : 'callable';
|
|
425
|
+
const hit = resolveInScope(ex.file, r.targetName, want, ex.lang);
|
|
258
426
|
emit({
|
|
259
427
|
source: r.sourceId,
|
|
260
|
-
target:
|
|
428
|
+
target: hit?.target ?? `symbol:${r.targetName}`,
|
|
261
429
|
relation: 'calls',
|
|
262
|
-
|
|
430
|
+
// An UNRESOLVED name stays EXTRACTED: `symbol:strlen` claims
|
|
431
|
+
// nothing about a node, it records that a call to a name this
|
|
432
|
+
// repository does not declare happened here. What is no longer
|
|
433
|
+
// EXTRACTED is a claim about a SPECIFIC in-repo node reached by
|
|
434
|
+
// name alone.
|
|
435
|
+
confidence: hit?.confidence ?? 'EXTRACTED',
|
|
263
436
|
});
|
|
264
437
|
return;
|
|
265
438
|
}
|
|
@@ -287,6 +460,27 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
287
460
|
}
|
|
288
461
|
// scoped (Class::m unresolved / facade) or dynamic ($obj->m) → AMBIGUOUS
|
|
289
462
|
const cands = methodCandidates(r.targetName, ex.lang);
|
|
463
|
+
if (r.callStyle === 'dynamic' && cands.length === 0) {
|
|
464
|
+
// `x.push()`, `m.get()`, `s.join()` — a dynamic member call whose
|
|
465
|
+
// method name matches NO method declared in this repository. The
|
|
466
|
+
// only target available is `symbol:push`, which is not a node, so
|
|
467
|
+
// no query verb can reach it: `affected` and `path` resolve seeds
|
|
468
|
+
// against real nodes and a pseudo-node is never one. It was 257 of
|
|
469
|
+
// 660 edges on this engine's own source — 39 % of the graph
|
|
470
|
+
// asserting "a method named push was called somewhere".
|
|
471
|
+
//
|
|
472
|
+
// NOT suppressed: a `scoped` call (`Cache::get()`) and a
|
|
473
|
+
// `hierarchy-unresolved` `$this->missing()` both name a real
|
|
474
|
+
// unresolved intent inside a known receiver, which a later
|
|
475
|
+
// resolution pass could settle. This drops only the class where
|
|
476
|
+
// there is nothing to settle.
|
|
477
|
+
//
|
|
478
|
+
// The count is published in `suppressed_edge_counts` — an engine
|
|
479
|
+
// that silently deletes its worst edges reports a flattering
|
|
480
|
+
// confidence ratio and no evidence.
|
|
481
|
+
suppressedKeys.add(`${r.sourceId}\0calls\0symbol:${r.targetName}`);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
290
484
|
emit({
|
|
291
485
|
source: r.sourceId,
|
|
292
486
|
target: ambiguousTarget(cands, r.targetName),
|
|
@@ -310,6 +504,7 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
310
504
|
|
|
311
505
|
const counts = { EXTRACTED: 0, INFERRED: 0, AMBIGUOUS: 0 };
|
|
312
506
|
for (const e of edges) counts[e.confidence] += 1;
|
|
507
|
+
const suppressed = { dynamic_no_candidate: suppressedKeys.size };
|
|
313
508
|
|
|
314
509
|
const languages = [...new Set(extracts.map((e) => e.lang))].sort() as Lang[];
|
|
315
510
|
|
|
@@ -328,6 +523,7 @@ export function buildGraph(files: readonly SourceFile[], extracts: readonly File
|
|
|
328
523
|
languages,
|
|
329
524
|
grammar_abi: EXPECTED_GRAMMAR_ABI,
|
|
330
525
|
edge_confidence_counts: counts,
|
|
526
|
+
suppressed_edge_counts: suppressed,
|
|
331
527
|
nodes,
|
|
332
528
|
edges,
|
|
333
529
|
};
|
|
@@ -370,6 +566,7 @@ export function serializeGraph(g: CodeGraph): string {
|
|
|
370
566
|
'languages',
|
|
371
567
|
'grammar_abi',
|
|
372
568
|
'edge_confidence_counts',
|
|
569
|
+
'suppressed_edge_counts',
|
|
373
570
|
'nodes',
|
|
374
571
|
'edges',
|
|
375
572
|
];
|