@event4u/agent-config 14.21.0 → 14.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +198 -0
- package/CREDITS.md +37 -0
- package/dist/agent-src/commands/analyze/inbox.md +380 -5
- package/dist/agent-src/commands/analyze/repo.md +4 -2
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
- package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
- package/dist/agent-src/rules/token-efficiency.md +26 -3
- package/dist/agent-src/scripts/_cli_entry.ts +51 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
- package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/dist/agent-src/scripts/stubs_due.ts +4 -18
- package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
- package/dist/agent-src/templates/agent-settings.md +0 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/_cli_entry.js +8 -0
- package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
- package/dist/cli-delegate/archival_sweep.js +8 -0
- package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
- package/dist/cli-delegate/check_update_banner.js +923 -0
- package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
- package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
- package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-BIXEDQPZ.js} +6 -0
- package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
- package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
- package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
- package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
- package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
- package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
- package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
- package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
- package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
- package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
- package/dist/cli-delegate/cmd_conformance.js +2 -2
- package/dist/cli-delegate/cmd_export.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +26 -2
- package/dist/cli-delegate/cmd_prune.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- 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/cli-delegate/dashboard_mode.js +16 -0
- package/dist/cli-delegate/gate_budget.js +24 -0
- package/dist/cli-delegate/gate_execute.js +25 -0
- package/dist/cli-delegate/guarded_baseline.js +18 -0
- package/dist/cli-delegate/resume_probe.js +22 -0
- package/dist/cli-delegate/roadmap_gates.js +638 -0
- package/dist/cli-delegate/roadmap_set_step.js +173 -0
- package/dist/cli-delegate/stub_queue.js +47 -0
- package/dist/cli-delegate/stubs_due.js +38 -0
- package/dist/cli-delegate/ticket_bundles.js +59 -0
- package/dist/cli-delegate/update_roadmap_progress.js +60 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +8 -8
- 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 +1193 -917
- package/dist/install/claudeLayerCarriage.js +237 -0
- package/dist/install/claudePathsPlan.js +79 -0
- package/dist/install/hostLayerFingerprint.js +1 -1
- package/dist/install/install.mjs +6 -0
- package/dist/install/partitionEligibility.js +231 -117
- package/dist/install/ruleLayerPartition.js +10 -7
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +39219 -38995
- package/dist/scripts/condense.js +32 -25
- package/docs/MIGRATION.md +49 -1
- package/docs/contracts/settings-classes.md +1 -1
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
- package/package.json +7 -4
- package/src/agent-src/scripts/_cli_entry.ts +51 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/src/agent-src/scripts/roadmap_gates.ts +4 -20
- package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/src/agent-src/scripts/stubs_due.ts +4 -18
- package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/src/config/continuity-surface.json +509 -0
- package/src/config/estate-count-budget.json +1 -1
- package/src/config/gate-coverage.yml +29 -8
- package/src/config/gate-violation-baselines.json +8 -8
- package/src/config/gitignore-block.txt +19 -0
- package/src/config/host-injection-effect.json +73 -0
- package/src/config/packed-binary-manifest.json +33 -0
- package/src/config/preamble-payload-budget.json +112 -105
- package/src/config/publish-surface.json +4 -2
- package/src/install/claudeLayerCarriage.ts +273 -0
- package/src/install/claudePathsPlan.ts +76 -0
- package/src/install/hostLayerFingerprint.ts +1 -1
- package/src/install/partitionEligibility.ts +261 -127
- package/src/install/ruleLayerPartition.ts +19 -23
- package/src/scripts/_dispatch.bash +77 -27
- package/src/scripts/_lib/auto_dispatch.ts +2 -2
- package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
- package/src/scripts/_lib/continuity_surface.ts +296 -0
- package/src/scripts/_lib/corpus_manifest.ts +26 -9
- package/src/scripts/_lib/hook_settings.ts +1 -1
- package/src/scripts/_lib/injection_effect.ts +151 -0
- package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
- package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
- package/src/scripts/_lib/pin_resolver.ts +13 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
- package/src/scripts/_lib/retired_status.ts +74 -0
- package/src/scripts/_lib/skill_catalogue.ts +156 -21
- package/src/scripts/_lib/subagent_capsule.ts +117 -8
- package/src/scripts/ai_council/api_transport.ts +5 -0
- package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
- package/src/scripts/ai_council/prompts.ts +9 -0
- package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
- package/src/scripts/capture_skill_catalogue.ts +24 -11
- package/src/scripts/check_candidate_lines.ts +499 -0
- package/src/scripts/check_continuity_surface.ts +475 -0
- package/src/scripts/check_dependency_floors.ts +13 -6
- package/src/scripts/check_estate_count.ts +40 -79
- package/src/scripts/check_pack_size.ts +262 -23
- package/src/scripts/check_roadmap_trackable.ts +19 -16
- package/src/scripts/check_rule_layer_partition.ts +54 -34
- package/src/scripts/check_single_delivery.ts +97 -31
- package/src/scripts/check_update_banner.ts +13 -0
- package/src/scripts/code_graph/build.ts +138 -15
- package/src/scripts/code_graph/cli.ts +76 -8
- package/src/scripts/code_graph/extract.ts +11 -0
- package/src/scripts/code_graph/grammar_probe.ts +43 -0
- package/src/scripts/code_graph/loader.ts +88 -14
- package/src/scripts/code_graph/query.ts +121 -11
- package/src/scripts/code_graph/resolution_tiers.ts +238 -0
- package/src/scripts/code_graph/sqlite_store.ts +247 -6
- package/src/scripts/code_graph/types.ts +66 -1
- package/src/scripts/code_graph/validate.ts +23 -0
- package/src/scripts/condense.ts +34 -22
- package/src/scripts/corpus_manifest.ts +5 -5
- package/src/scripts/council_cli.ts +11 -12
- package/src/scripts/count_intent_disagreement.ts +426 -0
- package/src/scripts/handoff_context_hook.ts +79 -0
- package/src/scripts/hook_effect_doctor.ts +82 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +64 -12
- package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
- package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
- package/src/scripts/hooks/concern_registry.ts +4 -2
- package/src/scripts/hooks/host_semantics.ts +1 -1
- package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
- package/src/scripts/hooks/rule_inject_hook.ts +1 -1
- package/src/scripts/hooks/skill_route_hook.ts +11 -4
- package/src/scripts/inbox_source_census.ts +322 -0
- package/src/scripts/install-hooks.sh +82 -0
- package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
- package/src/scripts/lint_plan_risk_register.ts +13 -17
- package/src/scripts/mcp_server/suggest_skill.ts +72 -0
- package/src/scripts/mcp_server/tools.ts +2 -56
- package/src/scripts/media/lib/adapter-contract.md +7 -2
- package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
- package/src/scripts/rdp_candidates_delta.ts +276 -0
- package/src/scripts/rdp_quality_eval.ts +217 -19
- package/src/scripts/release_mix_taxonomy.json +3 -2
- package/src/scripts/report_host_injection_effect.ts +189 -0
- package/src/scripts/report_layer_overlap.ts +41 -0
- package/src/scripts/report_skill_menu_census.ts +341 -0
- package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
- package/src/vendor/grammars/README.md +61 -0
- package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/src/scripts/_lib/carrier_status.ts +0 -118
- package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
- package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `status: carrier` predicate, and the reasoning five readers share.
|
|
3
|
-
*
|
|
4
|
-
* A carrier is a roadmap whose job is to hold obligations deferred out of a
|
|
5
|
-
* parent that is already archived. Every item in it has an unmet resumption
|
|
6
|
-
* trigger, and it has no `## Phase` headings, so it is skipped wherever a draft
|
|
7
|
-
* is: the dashboard, `/roadmap:process-*`, the trackability check and the plan
|
|
8
|
-
* risk register. It differs from a draft in what it costs to remove — a draft
|
|
9
|
-
* may be deleted freely, a carrier may not.
|
|
10
|
-
*
|
|
11
|
-
* This module exists because the predicate had two implementations within one
|
|
12
|
-
* change and five callers within one week. Two implementations of one frontmatter
|
|
13
|
-
* test drift, and the one nobody runs locally drifts first — the same
|
|
14
|
-
* memory-twin failure this repository has already recorded against a fixture
|
|
15
|
-
* corpus. One test, one place, and the reasoning above lives beside it rather
|
|
16
|
-
* than being restated in each caller.
|
|
17
|
-
*
|
|
18
|
-
* The read is frontmatter-scoped on purpose. A fenced `status: carrier` inside a
|
|
19
|
-
* roadmap that documents the syntax is not a declaration, and a body-wide match
|
|
20
|
-
* turned exactly that into a hard failure before a review caught it.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/** The frontmatter values that mean "not schedulable work, and not disposable". */
|
|
24
|
-
export const CARRIER_VALUES: ReadonlySet<string> = new Set(['carrier']);
|
|
25
|
-
|
|
26
|
-
/** The leading `---\n…\n---` block, or null when the text carries none. */
|
|
27
|
-
export function frontmatterBlock(text: string): string | null {
|
|
28
|
-
const m = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
29
|
-
return m === null ? null : (m[1] ?? '');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** True when `text`'s FRONTMATTER declares one of {@link CARRIER_VALUES}. */
|
|
33
|
-
export function declaresCarrier(text: string | null | undefined): boolean {
|
|
34
|
-
if (text === null || text === undefined) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
const fm = frontmatterBlock(text);
|
|
38
|
-
if (fm === null) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
const m = /^status:[ \t]*([A-Za-z-]+)[ \t]*$/m.exec(fm);
|
|
42
|
-
return m !== null && CARRIER_VALUES.has((m[1] ?? '').toLowerCase());
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Count the top-level roadmaps in `roadmapRoot` that declare themselves carriers.
|
|
47
|
-
*
|
|
48
|
-
* Lives here rather than in the estate gate because it is the same fact as the
|
|
49
|
-
* predicate above, read over a directory instead of a string, and the gate that
|
|
50
|
-
* needs it is already at its source-size ceiling.
|
|
51
|
-
*
|
|
52
|
-
* @param isCandidate the caller's own roadmap-filename test, so this module does
|
|
53
|
-
* not acquire a second opinion about what counts as a roadmap.
|
|
54
|
-
*/
|
|
55
|
-
export function countDeclaredCarriers(
|
|
56
|
-
roadmapRoot: string,
|
|
57
|
-
isCandidate: (name: string) => boolean,
|
|
58
|
-
fsLike: {
|
|
59
|
-
readdirSync: (p: string) => string[];
|
|
60
|
-
statSync: (p: string) => { isFile: () => boolean };
|
|
61
|
-
readFileSync: (p: string, enc: 'utf-8') => string;
|
|
62
|
-
},
|
|
63
|
-
join: (...parts: string[]) => string,
|
|
64
|
-
): number {
|
|
65
|
-
let names: string[];
|
|
66
|
-
try {
|
|
67
|
-
names = fsLike.readdirSync(roadmapRoot);
|
|
68
|
-
} catch {
|
|
69
|
-
return 0;
|
|
70
|
-
}
|
|
71
|
-
let n = 0;
|
|
72
|
-
for (const name of names) {
|
|
73
|
-
if (!name.endsWith('.md') || !isCandidate(name)) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const abs = join(roadmapRoot, name);
|
|
77
|
-
try {
|
|
78
|
-
if (!fsLike.statSync(abs).isFile()) {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (declaresCarrier(fsLike.readFileSync(abs, 'utf-8'))) {
|
|
82
|
-
n += 1;
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return n;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Fixture design — why two of the estate self-test cases are shaped as they are.
|
|
93
|
-
*
|
|
94
|
-
* The reasoning behind two of `check_estate_count`'s self-test cases, kept here
|
|
95
|
-
* because the gate that holds them is at its source-size ceiling and because
|
|
96
|
-
* both facts are about the carrier status rather than about that gate.
|
|
97
|
-
*
|
|
98
|
-
* **The deletion case's addition is a DRAFT, and that is the whole design.**
|
|
99
|
-
* A carrier holds obligations lifted out of an already-archived parent, so
|
|
100
|
-
* paying an offset for removing one rewards the loss: the deletion buys nothing
|
|
101
|
-
* and the addition stays unpaid. With an *ordinary* addition the growth half and
|
|
102
|
-
* the unpaid half coincide — growth iff `n > a`, unpaid iff `n > a` — so the
|
|
103
|
-
* case rejected on two independent grounds and stayed red with the carrier
|
|
104
|
-
* branch reverted, asserting nothing about the feature it named. A draft is
|
|
105
|
-
* invisible to `collect()`, so the count half cannot move and only the offset
|
|
106
|
-
* scoring decides.
|
|
107
|
-
*
|
|
108
|
-
* **The count case asserts the flip BACK, not the flip TO.** A reclassification
|
|
109
|
-
* is not a disposal in either direction, but the flip *to* carrier cannot be
|
|
110
|
-
* asserted by exit code: it lowers the count, a count below the floor is a
|
|
111
|
-
* drawdown, and a drawdown is free whichever way carriers are counted. Its
|
|
112
|
-
* consequence is assertable — with carriers uncounted the flip lowers the floor,
|
|
113
|
-
* so flipping the same file back reads as growth and must be paid for. That is
|
|
114
|
-
* the fixture's state, with the carrier already in the base tree, and it rejects
|
|
115
|
-
* with the carrier term removed from the count. The neutrality of the forward
|
|
116
|
-
* flip is asserted directly in `check_estate_count.test.ts`, where a count is
|
|
117
|
-
* readable.
|
|
118
|
-
*/
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The three `check_estate_count` self-test cases that exercise `status: carrier`.
|
|
3
|
-
*
|
|
4
|
-
* They live outside that gate because it sits at the 1,500-line source ceiling
|
|
5
|
-
* `check_source_size_budget` enforces, and because the doctrine that gate states
|
|
6
|
-
* for an oversized file is a split rather than a raised baseline. The reasoning
|
|
7
|
-
* behind the fixture shapes is in `carrier_status.ts` § Fixture design.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { SelfTestCase } from './gate_self_test.js';
|
|
11
|
-
|
|
12
|
-
/** What the caller must supply: its own fixture harness and roadmap bodies. */
|
|
13
|
-
export interface EstateCaseDeps {
|
|
14
|
-
fixture: (opts: {
|
|
15
|
-
roadmaps: number;
|
|
16
|
-
base?: string;
|
|
17
|
-
before?: (dir: string) => void;
|
|
18
|
-
after: (dir: string) => void;
|
|
19
|
-
}) => number;
|
|
20
|
-
write: (dir: string, rel: string, body: string) => void;
|
|
21
|
-
remove: (dir: string, rel: string) => void;
|
|
22
|
-
roadmap: (name: string, extra?: string) => string;
|
|
23
|
-
CARRIER: string;
|
|
24
|
-
DRAFT: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function carrierEstateCases(d: EstateCaseDeps): SelfTestCase[] {
|
|
28
|
-
const { fixture, write, remove, roadmap, CARRIER, DRAFT } = d;
|
|
29
|
-
return [
|
|
30
|
-
{
|
|
31
|
-
name: 'deleting a carrier does not offset a new draft → reject',
|
|
32
|
-
expect: 'reject',
|
|
33
|
-
run: () =>
|
|
34
|
-
fixture({
|
|
35
|
-
roadmaps: 3,
|
|
36
|
-
base: 'main',
|
|
37
|
-
before: (dir) => write(dir, 'agents/roadmaps/road-to-carrier.md', CARRIER),
|
|
38
|
-
after: (dir) => {
|
|
39
|
-
remove(dir, 'agents/roadmaps/road-to-carrier.md');
|
|
40
|
-
write(dir, 'agents/roadmaps/road-to-new.md', DRAFT);
|
|
41
|
-
},
|
|
42
|
-
}),
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
// The control that proves the case above tests the STATUS rather
|
|
46
|
-
// than the deletion: only the removed file's status differs.
|
|
47
|
-
name: 'deleting an ordinary roadmap still offsets a new draft → accept',
|
|
48
|
-
expect: 'accept',
|
|
49
|
-
run: () =>
|
|
50
|
-
fixture({
|
|
51
|
-
roadmaps: 3,
|
|
52
|
-
base: 'main',
|
|
53
|
-
before: (dir) => write(dir, 'agents/roadmaps/road-to-spare.md', roadmap('S')),
|
|
54
|
-
after: (dir) => {
|
|
55
|
-
remove(dir, 'agents/roadmaps/road-to-spare.md');
|
|
56
|
-
write(dir, 'agents/roadmaps/road-to-new.md', DRAFT);
|
|
57
|
-
},
|
|
58
|
-
}),
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'flipping a carrier back to an ordinary roadmap costs nothing → accept',
|
|
62
|
-
expect: 'accept',
|
|
63
|
-
run: () =>
|
|
64
|
-
fixture({
|
|
65
|
-
roadmaps: 3,
|
|
66
|
-
base: 'main',
|
|
67
|
-
before: (dir) => write(dir, 'agents/roadmaps/road-to-2.md', CARRIER),
|
|
68
|
-
after: (dir) => write(dir, 'agents/roadmaps/road-to-2.md', roadmap('R2')),
|
|
69
|
-
}),
|
|
70
|
-
},
|
|
71
|
-
];
|
|
72
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
/**
|
|
3
|
-
* PreToolUse code-graph nudge (ADR-124 Phase 4) — deterministic, warn-only.
|
|
4
|
-
*
|
|
5
|
-
* When a code-graph source is available and the agent is about to search or
|
|
6
|
-
* read source files, surface a one-line reminder to query the graph first.
|
|
7
|
-
* NEVER blocks (dispatcher contract: 0 allow · 2 warn) — Source G's strict
|
|
8
|
-
* block-the-first-read mode is deliberately NOT ported (blocking reads on a
|
|
9
|
-
* possibly-stale index violates the minimal-safe-diff posture). Once per
|
|
10
|
-
* session (a latch keyed by session id), so it cannot nag.
|
|
11
|
-
*
|
|
12
|
-
* Default-OFF. Fires only when `hooks.code_graph.enabled: true`. Disabled /
|
|
13
|
-
* missing / already-latched → no-op exit 0. fail_closed: false — any error
|
|
14
|
-
* returns allow; a token optimisation must never break a tool call.
|
|
15
|
-
*
|
|
16
|
-
* Branches (first eligible search/read this session):
|
|
17
|
-
* present + fresh → "query the graph first"
|
|
18
|
-
* present + stale → "index N commits behind — rebuild"
|
|
19
|
-
* absent → "no graph — build one?"
|
|
20
|
-
*/
|
|
21
|
-
import fs from 'node:fs';
|
|
22
|
-
import path from 'node:path';
|
|
23
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
24
|
-
|
|
25
|
-
import { detectSources, pickSource } from '../code_graph/detect.js';
|
|
26
|
-
import { readHookStdin } from './hook_stdin.js';
|
|
27
|
-
|
|
28
|
-
const SETTINGS_FILE = '.agent-settings.yml';
|
|
29
|
-
const EXIT_ALLOW = 0;
|
|
30
|
-
const EXIT_WARN = 2;
|
|
31
|
-
const CODE_EXT = /\.(php|ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i;
|
|
32
|
-
|
|
33
|
-
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
34
|
-
type JsonObject = { [k: string]: JsonValue };
|
|
35
|
-
|
|
36
|
-
function isObject(v: unknown): v is JsonObject {
|
|
37
|
-
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** `hooks.code_graph.enabled: true` mini-parser (mirrors rtk_wrap_hook). */
|
|
41
|
-
export function enabled(root: string): boolean {
|
|
42
|
-
const f = path.join(root, SETTINGS_FILE);
|
|
43
|
-
let text: string;
|
|
44
|
-
try {
|
|
45
|
-
if (!fs.statSync(f).isFile()) return false;
|
|
46
|
-
text = fs.readFileSync(f, 'utf-8');
|
|
47
|
-
} catch {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
let inHooks = false;
|
|
51
|
-
let inCg = false;
|
|
52
|
-
for (const raw of text.split(/\r\n|\r|\n/)) {
|
|
53
|
-
const line = raw.replace(/\s+$/, '');
|
|
54
|
-
if (!line || line.replace(/^\s+/, '').startsWith('#')) continue;
|
|
55
|
-
if (!(line.startsWith(' ') || line.startsWith('\t'))) {
|
|
56
|
-
inHooks = /^hooks\s*:\s*$/.test(line);
|
|
57
|
-
inCg = false;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (inHooks) {
|
|
61
|
-
if (/^\s+code_graph\s*:\s*$/.test(line)) {
|
|
62
|
-
inCg = true;
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (inCg && /^\s{0,3}\S/.test(line)) inCg = false;
|
|
66
|
-
}
|
|
67
|
-
if (inCg && /^\s+enabled\s*:\s*true\b/.test(line)) return true;
|
|
68
|
-
}
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
interface ToolIntent {
|
|
73
|
-
isSearch: boolean;
|
|
74
|
-
isCodeRead: boolean;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Best-effort read of the intercepted tool + its target from the envelope. */
|
|
78
|
-
export function classifyTool(envelope: JsonObject): ToolIntent {
|
|
79
|
-
const payload = isObject(envelope['payload']) ? envelope['payload'] : envelope;
|
|
80
|
-
const nameVal =
|
|
81
|
-
payload['tool_name'] ?? payload['toolName'] ?? payload['tool'] ?? envelope['tool_name'] ?? envelope['tool'];
|
|
82
|
-
const name = typeof nameVal === 'string' ? nameVal : '';
|
|
83
|
-
const ti = (isObject(payload['tool_input']) ? payload['tool_input'] : envelope['tool_input']) as
|
|
84
|
-
| JsonObject
|
|
85
|
-
| undefined;
|
|
86
|
-
const isSearch = name === 'Grep' || name === 'Glob';
|
|
87
|
-
let isCodeRead = false;
|
|
88
|
-
if (name === 'Read' && isObject(ti)) {
|
|
89
|
-
const fp = ti['file_path'] ?? ti['path'];
|
|
90
|
-
if (typeof fp === 'string') isCodeRead = CODE_EXT.test(fp);
|
|
91
|
-
}
|
|
92
|
-
return { isSearch, isCodeRead };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function sessionId(envelope: JsonObject): string {
|
|
96
|
-
const s = envelope['session_id'] ?? envelope['sessionId'];
|
|
97
|
-
return typeof s === 'string' && s ? s : 'default';
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function latchFile(root: string): string {
|
|
101
|
-
return path.join(root, 'agents', 'runtime', 'state', 'code-graph-nudge.json');
|
|
102
|
-
}
|
|
103
|
-
function alreadyNudged(root: string, session: string): boolean {
|
|
104
|
-
try {
|
|
105
|
-
const state = JSON.parse(fs.readFileSync(latchFile(root), 'utf-8')) as Record<string, boolean>;
|
|
106
|
-
return state[session] === true;
|
|
107
|
-
} catch {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function latch(root: string, session: string): void {
|
|
112
|
-
try {
|
|
113
|
-
const p = latchFile(root);
|
|
114
|
-
let state: Record<string, boolean> = {};
|
|
115
|
-
try {
|
|
116
|
-
state = JSON.parse(fs.readFileSync(p, 'utf-8')) as Record<string, boolean>;
|
|
117
|
-
} catch {
|
|
118
|
-
/* fresh */
|
|
119
|
-
}
|
|
120
|
-
state[session] = true;
|
|
121
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
122
|
-
fs.writeFileSync(p, JSON.stringify(state));
|
|
123
|
-
} catch {
|
|
124
|
-
/* fail-open: a persistence failure must not break the tool call */
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const NATIVE_CACHE = path.join('agents', 'runtime', 'state', 'code-graph-v1.json');
|
|
129
|
-
|
|
130
|
-
/** The nudge line for the current source state (≤40 tokens, one line). */
|
|
131
|
-
export function nudgeReason(root: string): string {
|
|
132
|
-
const verdicts = detectSources(root, path.join(root, NATIVE_CACHE));
|
|
133
|
-
const picked = pickSource(verdicts);
|
|
134
|
-
if (!picked) {
|
|
135
|
-
return 'No code-graph found. For "who calls / where used / impact" run `agent-config code-graph refresh` once, then `agent-config code-graph query <symbol>` instead of grepping blind.';
|
|
136
|
-
}
|
|
137
|
-
if (picked.stale) {
|
|
138
|
-
return `code-graph is ${picked.commits_behind ?? 'N'} commit(s) behind — rebuild with \`agent-config code-graph refresh\` before trusting relationship answers.`;
|
|
139
|
-
}
|
|
140
|
-
return 'A fresh code-graph is present — for structure questions run `agent-config code-graph query|affected <symbol>` first; grep is the fallback (say which answered).';
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function main(): number {
|
|
144
|
-
let envelope: JsonValue;
|
|
145
|
-
try {
|
|
146
|
-
const raw = readHookStdin();
|
|
147
|
-
envelope = raw.trim() ? (JSON.parse(raw) as JsonValue) : {};
|
|
148
|
-
} catch {
|
|
149
|
-
return EXIT_ALLOW;
|
|
150
|
-
}
|
|
151
|
-
if (!isObject(envelope)) return EXIT_ALLOW;
|
|
152
|
-
|
|
153
|
-
const cwd = envelope['cwd'];
|
|
154
|
-
const pr = envelope['workspace_root'] ?? envelope['project_root'];
|
|
155
|
-
const root = typeof cwd === 'string' && cwd ? cwd : typeof pr === 'string' && pr ? pr : '.';
|
|
156
|
-
if (!enabled(root)) return EXIT_ALLOW;
|
|
157
|
-
|
|
158
|
-
const { isSearch, isCodeRead } = classifyTool(envelope);
|
|
159
|
-
if (!isSearch && !isCodeRead) return EXIT_ALLOW;
|
|
160
|
-
|
|
161
|
-
const session = sessionId(envelope);
|
|
162
|
-
if (alreadyNudged(root, session)) return EXIT_ALLOW; // once per session
|
|
163
|
-
|
|
164
|
-
let reason: string;
|
|
165
|
-
try {
|
|
166
|
-
reason = nudgeReason(root);
|
|
167
|
-
} catch {
|
|
168
|
-
return EXIT_ALLOW;
|
|
169
|
-
}
|
|
170
|
-
latch(root, session);
|
|
171
|
-
process.stdout.write(`${JSON.stringify({ decision: 'warn', reason })}\n`);
|
|
172
|
-
return EXIT_WARN;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Bundle-safety: never auto-run when inlined into an esbuild bundle, where
|
|
176
|
-
// every module shares the bundle's `import.meta.url` (see cmd_migrate.ts).
|
|
177
|
-
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
178
|
-
function isCliEntry(): boolean {
|
|
179
|
-
if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
if (process.argv[1] === undefined) return false;
|
|
183
|
-
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
184
|
-
if (import.meta.url === argvUrl) return true;
|
|
185
|
-
try {
|
|
186
|
-
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
187
|
-
} catch {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (isCliEntry()) process.exit(main());
|