@event4u/agent-config 17.2.1 → 17.4.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/.claude-plugin/skills/install-agent-config/SKILL.md +9 -9
- package/CHANGELOG.md +167 -193
- package/CREDITS.md +2 -2
- package/README.md +1 -1
- package/dist/agent-src/skills/code-intelligence/SKILL.md +16 -12
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/check_memory.ts +1 -1
- package/dist/cli/commands/recordTriggerEval.js +1 -1
- package/dist/cli/registry.js +2 -2
- package/dist/cli-delegate/{chunk-KR5ZIFPO.js → chunk-5AYJGUA3.js} +1 -3
- package/dist/cli-delegate/chunk-DIFZBP4G.js +1336 -0
- package/dist/cli-delegate/chunk-E7ACTJTJ.js +6792 -0
- package/dist/cli-delegate/{chunk-VYZGJLCA.js → chunk-RKNT4LIK.js} +22 -1286
- package/dist/cli-delegate/{chunk-BZJ4Y3CI.js → chunk-WY5ZZPGL.js} +2 -2
- package/dist/cli-delegate/{chunk-CMZEKSNN.js → chunk-X6LZ7W5A.js} +73 -10
- package/dist/cli-delegate/cmd_conformance.js +190 -192
- package/dist/cli-delegate/cmd_doctor.js +5 -5
- package/dist/cli-delegate/cmd_doctor_neighbours.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +2 -2
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +8 -4
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_update.js +24 -13
- package/dist/cli-delegate/cmd_upgrade.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/cli-delegate/dist-25AZAHOP.js +3240 -0
- package/dist/cli-delegate/dist-P7MFPMF3.js +411 -0
- package/dist/cli-delegate/dist-XAS5ZLQB.js +6 -0
- package/dist/cli-delegate/{upgrade_unattended-VVL6JLSX.js → upgrade_unattended-QCWOKTUN.js} +178 -163
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +3 -3
- 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/code_graph_refresh.chunk.cjs +443 -0
- package/dist/hooks/code_graph_refresh.chunk.cjs.sha256 +1 -0
- package/dist/hooks/dispatch.js +1001 -938
- package/dist/hooks/dispatch.sha256 +1 -1
- package/dist/hooks/egress_engine.chunk.cjs +57 -7
- package/dist/hooks/egress_engine.chunk.cjs.sha256 +1 -1
- package/dist/hooks/update_notice.chunk.cjs +914 -0
- package/dist/hooks/update_notice.chunk.cjs.sha256 +1 -0
- package/dist/install/install.mjs +6974 -6037
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -0
- package/dist/mcp/registry-manifest.json +3 -2
- package/dist/mcp/server.json +13 -4
- package/dist/mcp/server.mjs +71202 -63282
- package/dist/scripts/_lib/egress_rules.js +8 -2
- package/dist/scripts/condense.js +18 -15
- package/dist/server/routes/discovery.js +1 -1
- package/dist/server/routes/install.js +1 -1
- package/dist/server/routes/ping.js +1 -1
- package/dist/server/routes/schema.js +4 -4
- package/dist/server/routes/settings.js +2 -6
- package/dist/server/routes/wizard.js +6 -6
- package/dist/server/schemas/settings.js +42 -42
- package/dist/shared/settingsSurface.js +1 -1
- package/dist/shared/userMd/schema.js +1 -1
- package/dist/shared/zodJsonSchema.js +47 -0
- package/dist/ui/assets/index-B0bcIgi0.js +77 -0
- package/dist/ui/index.html +1 -1
- package/package.json +6 -3
- package/src/agent-src/templates/scripts/check_memory.ts +1 -1
- package/src/config/ci-local-parity.yml +11 -0
- package/src/config/continuity-surface.json +9 -2
- package/src/config/evaluator-budgets.json +9 -8
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/gitignore-block.txt +6 -5
- package/src/config/hook-bundle-budget.json +46 -6
- package/src/config/pack-size-budget.json +12 -1
- package/src/config/packed-binary-manifest.json +18 -0
- package/src/config/sigstore/npm_keys.json +50 -0
- package/src/config/sigstore/trust-material.json +9 -0
- package/src/config/sigstore/trusted_root.json +126 -0
- package/src/config/skill-body-budget.json +7 -1
- package/src/scripts/_cli/cmd_settings_set.ts +3 -2
- package/src/scripts/_cli/cmd_update.ts +48 -20
- package/src/scripts/_cli/cmd_upgrade.ts +7 -1
- package/src/scripts/_cli/upgrade_unattended.ts +90 -76
- package/src/scripts/_dispatch.bash +4 -3
- package/src/scripts/_lib/confusables.ts +0 -19
- package/src/scripts/_lib/egress_normalize.ts +53 -23
- package/src/scripts/_lib/egress_rules.ts +8 -2
- package/src/scripts/_lib/egress_text.ts +5 -3
- package/src/scripts/_lib/egress_token.ts +7 -5
- package/src/scripts/_lib/gate_collector_experimental.ts +633 -0
- package/src/scripts/_lib/git_common_dir.ts +10 -0
- package/src/scripts/_lib/graph_feeder_record.ts +3 -2
- package/src/scripts/_lib/install_reach_checks.ts +32 -2
- package/src/scripts/_lib/lineage_backfill.ts +136 -0
- package/src/scripts/_lib/lineage_ledger.ts +211 -0
- package/src/scripts/_lib/lineage_map.ts +55 -0
- package/src/scripts/_lib/lineage_query.ts +140 -0
- package/src/scripts/_lib/lineage_seed.ts +116 -0
- package/src/scripts/_lib/lineage_watch.ts +249 -0
- package/src/scripts/_lib/link_crypto.ts +8 -4
- package/src/scripts/_lib/listing_manifests.ts +85 -0
- package/src/scripts/_lib/neighbour_census.ts +1 -0
- package/src/scripts/_lib/production_reach.ts +3 -3
- package/src/scripts/_lib/project_registry.ts +227 -0
- package/src/scripts/_lib/provenance_verify.ts +14 -242
- package/src/scripts/_lib/release_settings_surface.ts +2 -6
- package/src/scripts/_lib/sigstore_provenance.ts +177 -0
- package/src/scripts/_lib/source_id.ts +48 -0
- package/src/scripts/_lib/source_shape.ts +4 -0
- package/src/scripts/_lib/unattended_gates.ts +6 -1
- package/src/scripts/adoption_status.ts +231 -27
- package/src/scripts/advisories.ts +26 -10
- package/src/scripts/ai_council/council_egress.ts +5 -6
- package/src/scripts/bench_graph_feeder_latency.ts +10 -3
- package/src/scripts/build_mcp_registry_manifest.ts +23 -12
- package/src/scripts/check_dependency_floors.ts +14 -0
- package/src/scripts/check_egress_mutations.ts +1 -0
- package/src/scripts/check_release_manifest_content.ts +17 -2
- package/src/scripts/check_release_pr_shape.ts +5 -0
- package/src/scripts/code_graph/build.ts +145 -45
- package/src/scripts/code_graph/cli.ts +382 -35
- package/src/scripts/code_graph/conventions_default.ts +247 -0
- package/src/scripts/code_graph/detect.ts +5 -5
- package/src/scripts/code_graph/estate_cli.ts +135 -0
- package/src/scripts/code_graph/estate_index.ts +394 -0
- package/src/scripts/code_graph/estate_manifests.ts +223 -0
- package/src/scripts/code_graph/estate_map.ts +141 -0
- package/src/scripts/code_graph/extract.ts +8 -2
- package/src/scripts/code_graph/extract_more.ts +241 -2
- package/src/scripts/code_graph/graph_budget.ts +158 -0
- package/src/scripts/code_graph/graph_home.ts +80 -0
- package/src/scripts/code_graph/lineage_cli.ts +315 -0
- package/src/scripts/code_graph/loader.ts +10 -2
- package/src/scripts/code_graph/map.ts +195 -53
- package/src/scripts/code_graph/map_model.ts +113 -20
- package/src/scripts/code_graph/map_verify.ts +80 -5
- package/src/scripts/code_graph/map_viewer/viewer.css +18 -0
- package/src/scripts/code_graph/map_viewer/viewer.js +146 -15
- package/src/scripts/code_graph/passes.ts +173 -0
- package/src/scripts/code_graph/project_ref.ts +25 -0
- package/src/scripts/code_graph/projects_cli.ts +254 -0
- package/src/scripts/code_graph/providers/angular.ts +55 -0
- package/src/scripts/code_graph/providers/aspnetcore.ts +182 -0
- package/src/scripts/code_graph/providers/catalog.ts +299 -0
- package/src/scripts/code_graph/providers/django.ts +206 -0
- package/src/scripts/code_graph/providers/express.ts +25 -0
- package/src/scripts/code_graph/providers/fastapi.ts +49 -0
- package/src/scripts/code_graph/providers/flask.ts +99 -0
- package/src/scripts/code_graph/providers/go_http.ts +121 -0
- package/src/scripts/code_graph/providers/index.ts +103 -0
- package/src/scripts/code_graph/{laravel_overlay.ts → providers/laravel.ts} +113 -7
- package/src/scripts/code_graph/providers/nestjs.ts +34 -0
- package/src/scripts/code_graph/providers/nextjs.ts +91 -0
- package/src/scripts/code_graph/providers/nuxt.ts +55 -0
- package/src/scripts/code_graph/providers/rails.ts +204 -0
- package/src/scripts/code_graph/providers/react-router.ts +58 -0
- package/src/scripts/code_graph/providers/shared/detect.ts +78 -0
- package/src/scripts/code_graph/providers/shared/extractors.ts +249 -0
- package/src/scripts/code_graph/providers/shared/js.ts +252 -0
- package/src/scripts/code_graph/providers/shared/kit.ts +210 -0
- package/src/scripts/code_graph/providers/shared/php.ts +64 -0
- package/src/scripts/code_graph/providers/shared/python.ts +223 -0
- package/src/scripts/code_graph/providers/spring.ts +175 -0
- package/src/scripts/code_graph/providers/sqlalchemy.ts +62 -0
- package/src/scripts/code_graph/providers/sveltekit.ts +59 -0
- package/src/scripts/code_graph/providers/symfony.ts +142 -0
- package/src/scripts/code_graph/providers/typeorm.ts +58 -0
- package/src/scripts/code_graph/providers/types.ts +56 -0
- package/src/scripts/code_graph/providers/vue-router.ts +21 -0
- package/src/scripts/code_graph/providers/wordpress.ts +128 -0
- package/src/scripts/code_graph/query.ts +9 -9
- package/src/scripts/code_graph/refresh_cli.ts +81 -0
- package/src/scripts/code_graph/refresh_lock.ts +197 -0
- package/src/scripts/code_graph/repo_identity.ts +169 -0
- package/src/scripts/code_graph/resolution_tiers.ts +19 -7
- package/src/scripts/code_graph/scip_reader.ts +2 -2
- package/src/scripts/code_graph/sqlite_store.ts +31 -28
- package/src/scripts/code_graph/types.ts +79 -41
- package/src/scripts/code_graph/validate.ts +6 -8
- package/src/scripts/condense.ts +15 -15
- package/src/scripts/generate_settings_reference.ts +2 -6
- package/src/scripts/hook_chunks.mjs +11 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +43 -23
- package/src/scripts/hooks/code_graph_refresh_hook.ts +163 -0
- package/src/scripts/hooks/dispatch_hook.ts +6 -7
- package/src/scripts/hooks/host_semantics.ts +72 -0
- package/src/scripts/hooks/lazy_concern.ts +8 -0
- package/src/scripts/hooks/user_notice.ts +105 -0
- package/src/scripts/install-hooks.sh +54 -33
- package/src/scripts/install.ts +2 -6
- package/src/scripts/lint_harvest_provenance.ts +137 -11
- package/src/scripts/lint_hook_manifest.ts +2 -0
- package/src/scripts/lint_marketplace.ts +33 -0
- package/src/scripts/lint_mcp_registry_manifest.ts +36 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +70 -15
- package/src/scripts/mcp_server/graph_tools.ts +165 -41
- package/src/scripts/network_sources.ts +24 -6
- package/src/scripts/release.ts +2 -5
- package/src/scripts/release_drill.ts +4 -1
- package/src/scripts/release_env.ts +17 -0
- package/src/scripts/release_mix_taxonomy.json +3 -0
- package/src/scripts/schemas/provenance-mechanism.schema.json +46 -0
- package/src/scripts/update_notice_hook.ts +75 -7
- package/src/server/schemas/settings.ts +42 -42
- package/src/shared/settingsSurface.ts +1 -1
- package/src/shared/userMd/schema.ts +1 -1
- package/src/shared/zodJsonSchema.ts +41 -0
- package/src/vendor/grammars/README.md +8 -6
- package/src/vendor/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/cli-delegate/chunk-XIAPY44N.js +0 -4587
- package/dist/ui/assets/index-w70Ado09.js +0 -23
- /package/dist/cli-delegate/{chunk-FKYZOWVD.js → chunk-QBAFP67O.js} +0 -0
- /package/dist/cli-delegate/{chunk-BMGUGSY6.js → chunk-TQG6UMPC.js} +0 -0
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
* It is fail-open on every path and returns 0 on every path, including an
|
|
33
33
|
* unreadable state file, an unwritable state directory and a missing changelog.
|
|
34
34
|
*/
|
|
35
|
+
import { randomUUID } from 'node:crypto';
|
|
35
36
|
import * as fs from 'node:fs';
|
|
36
37
|
import * as path from 'node:path';
|
|
37
38
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -48,6 +49,7 @@ import { parse_version } from './_lib/version_order.js';
|
|
|
48
49
|
import * as user_global_paths from './_lib/user_global_paths.js';
|
|
49
50
|
import { readHookStdin } from './hooks/hook_stdin.js';
|
|
50
51
|
import { is_replay_mode } from './hooks/state_io.js';
|
|
52
|
+
import { hostShowsUserNotice, readNoticeReceipt, USER_NOTICE_ID_KEY, USER_NOTICE_KEY, type NoticeReceipt } from './hooks/user_notice.js';
|
|
51
53
|
|
|
52
54
|
export const CONCERN = 'update-notice';
|
|
53
55
|
export const NOTICE_STATE_FILENAME = 'update-notice.json';
|
|
@@ -73,6 +75,15 @@ export const NOTICE_STATE_SCHEMA = 1;
|
|
|
73
75
|
export interface NoticeState {
|
|
74
76
|
readonly staged: string | null;
|
|
75
77
|
readonly last_seen_installed: string | null;
|
|
78
|
+
/**
|
|
79
|
+
* A notice handed to the dispatcher's user channel and not yet confirmed:
|
|
80
|
+
* the session it was handed over in, the id it was handed over with, and the
|
|
81
|
+
* installed version it reported. It becomes `last_seen_installed` only when
|
|
82
|
+
* the dispatcher's receipt names that session AND that id
|
|
83
|
+
* (`hooks/user_notice.ts`) — an older receipt from the same session is a
|
|
84
|
+
* receipt for another notice. Until then the notice is due again.
|
|
85
|
+
*/
|
|
86
|
+
readonly pending?: { readonly session_id: string; readonly notice_id: string; readonly installed: string };
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
export function readNoticeState(statePath: string | null): NoticeState {
|
|
@@ -82,12 +93,25 @@ export function readNoticeState(statePath: string | null): NoticeState {
|
|
|
82
93
|
const o = raw !== null && typeof raw === 'object' && !Array.isArray(raw) ? (raw as Record<string, unknown>) : {};
|
|
83
94
|
if (o['schema'] !== undefined && o['schema'] !== NOTICE_STATE_SCHEMA) return { staged: null, last_seen_installed: null };
|
|
84
95
|
const str = (v: unknown): string | null => (typeof v === 'string' && parse_version(v.trim()) !== null ? v.trim() : null);
|
|
85
|
-
|
|
96
|
+
const p = o['pending'] as Record<string, unknown> | null | undefined;
|
|
97
|
+
const installed = str(p?.['installed']);
|
|
98
|
+
const pending =
|
|
99
|
+
typeof p?.['session_id'] === 'string' && typeof p['notice_id'] === 'string' && installed !== null
|
|
100
|
+
? { session_id: p['session_id'], notice_id: p['notice_id'], installed }
|
|
101
|
+
: undefined;
|
|
102
|
+
return { staged: str(o['staged']), last_seen_installed: str(o['last_seen_installed']), ...(pending ? { pending } : {}) };
|
|
86
103
|
} catch {
|
|
87
104
|
return { staged: null, last_seen_installed: null };
|
|
88
105
|
}
|
|
89
106
|
}
|
|
90
107
|
|
|
108
|
+
/** The state with a pending notice promoted, when the dispatcher's receipt names its session and its id. */
|
|
109
|
+
export function confirmDelivered(state: NoticeState, receipt: NoticeReceipt | null): NoticeState {
|
|
110
|
+
const p = state.pending;
|
|
111
|
+
if (p === undefined || receipt === null || !receipt.notice_id || receipt.notice_id !== p.notice_id || receipt.session_id !== p.session_id) return state;
|
|
112
|
+
return { staged: state.staged, last_seen_installed: p.installed };
|
|
113
|
+
}
|
|
114
|
+
|
|
91
115
|
export function writeNoticeState(statePath: string, state: NoticeState): void {
|
|
92
116
|
try {
|
|
93
117
|
write_atomic(statePath, `${JSON.stringify({ schema: NOTICE_STATE_SCHEMA, ...state }, null, 2)}\n`);
|
|
@@ -138,11 +162,11 @@ function readText(p: string): string {
|
|
|
138
162
|
}
|
|
139
163
|
}
|
|
140
164
|
|
|
141
|
-
function
|
|
165
|
+
function envelopeField(raw: string, key: string): string | null {
|
|
142
166
|
try {
|
|
143
167
|
const doc: unknown = JSON.parse(raw);
|
|
144
|
-
const
|
|
145
|
-
return typeof
|
|
168
|
+
const value = (doc as Record<string, unknown> | null)?.[key];
|
|
169
|
+
return typeof value === 'string' && value.trim() !== '' ? value.trim() : null;
|
|
146
170
|
} catch {
|
|
147
171
|
return null;
|
|
148
172
|
}
|
|
@@ -160,6 +184,14 @@ export interface NoticeDeps {
|
|
|
160
184
|
readonly throttlePath: string;
|
|
161
185
|
readonly registerDir: string;
|
|
162
186
|
readonly err: { write(text: string): void };
|
|
187
|
+
/**
|
|
188
|
+
* The dispatcher's user channel (`hooks/user_notice.ts`), set only when the
|
|
189
|
+
* host shows a notice to the user. The lines then go there as one reply and
|
|
190
|
+
* nothing goes to stderr, which every host discards.
|
|
191
|
+
*/
|
|
192
|
+
readonly notice?: { write(text: string): void };
|
|
193
|
+
/** The dispatcher's delivery receipt for this concern, read only on the `notice` path. */
|
|
194
|
+
readonly receipt?: () => NoticeReceipt | null;
|
|
163
195
|
}
|
|
164
196
|
|
|
165
197
|
/**
|
|
@@ -173,7 +205,7 @@ export function runNotice(deps: NoticeDeps, apply = true): NoticeOutcome {
|
|
|
173
205
|
|
|
174
206
|
const probe = deps.probeStatePath === null ? {} : readJson(deps.probeStatePath);
|
|
175
207
|
const seen = probe['last_seen_version'];
|
|
176
|
-
const state = readNoticeState(deps.noticeStatePath);
|
|
208
|
+
const state = confirmDelivered(readNoticeState(deps.noticeStatePath), deps.receipt?.() ?? null);
|
|
177
209
|
|
|
178
210
|
const outcome = updateNotice({
|
|
179
211
|
installed,
|
|
@@ -200,6 +232,23 @@ export function runNotice(deps: NoticeDeps, apply = true): NoticeOutcome {
|
|
|
200
232
|
return outcome;
|
|
201
233
|
}
|
|
202
234
|
|
|
235
|
+
// Through the host, a notice counts as shown only once the dispatcher's
|
|
236
|
+
// receipt names this session and this notice's id; until then it stays
|
|
237
|
+
// pending and is said again.
|
|
238
|
+
if (deps.notice !== undefined) {
|
|
239
|
+
const noticeId = randomUUID();
|
|
240
|
+
if (outcome.lines.length > 0) {
|
|
241
|
+
deps.notice.write(JSON.stringify({ [USER_NOTICE_KEY]: outcome.lines.join('\n'), [USER_NOTICE_ID_KEY]: noticeId }));
|
|
242
|
+
}
|
|
243
|
+
writeNoticeState(deps.noticeStatePath, {
|
|
244
|
+
staged: outcome.stage,
|
|
245
|
+
last_seen_installed: state.last_seen_installed,
|
|
246
|
+
...(outcome.lines.length > 0 && deps.sessionId
|
|
247
|
+
? { pending: { session_id: deps.sessionId, notice_id: noticeId, installed } }
|
|
248
|
+
: {}),
|
|
249
|
+
});
|
|
250
|
+
return outcome;
|
|
251
|
+
}
|
|
203
252
|
for (const line of outcome.lines) deps.err.write(`[update] ${line}\n`);
|
|
204
253
|
writeNoticeState(deps.noticeStatePath, { staged: outcome.stage, last_seen_installed: installed });
|
|
205
254
|
return outcome;
|
|
@@ -214,6 +263,19 @@ export function runNotice(deps: NoticeDeps, apply = true): NoticeOutcome {
|
|
|
214
263
|
export function noticeOncePerDay(deps: NoticeDeps): NoticeOutcome | null {
|
|
215
264
|
const dry = runNotice(deps, false);
|
|
216
265
|
if (dry.suppressed !== null && !RECONCILE_REASONS.has(dry.suppressed)) return null;
|
|
266
|
+
if (deps.notice !== undefined) {
|
|
267
|
+
// Through the host the day throttle is a DELIVERED notice today, so a
|
|
268
|
+
// notice the dispatcher dropped is said again at the next session.
|
|
269
|
+
const receipt = deps.receipt?.() ?? null;
|
|
270
|
+
if (receipt !== null && localDay(new Date(receipt.at)) === localDay(new Date())) {
|
|
271
|
+
const stored = readNoticeState(deps.noticeStatePath);
|
|
272
|
+
const confirmed = confirmDelivered(stored, receipt);
|
|
273
|
+
if (confirmed !== stored) writeNoticeState(deps.noticeStatePath, confirmed);
|
|
274
|
+
// Another session's receipt never throttles a notice still pending.
|
|
275
|
+
if (confirmed.pending === undefined) return null;
|
|
276
|
+
}
|
|
277
|
+
return runNotice(deps);
|
|
278
|
+
}
|
|
217
279
|
if (ranToday(deps.throttlePath)) return null;
|
|
218
280
|
const outcome = runNotice(deps);
|
|
219
281
|
stampToday(deps.throttlePath);
|
|
@@ -261,11 +323,17 @@ export function main(argv?: string[]): number {
|
|
|
261
323
|
// The throttle stamp is a WRITE, so it comes after the decision that
|
|
262
324
|
// this context may be written in at all — AC-2 asks for zero writes
|
|
263
325
|
// under CI, a non-TTY, a source checkout and the opt-out.
|
|
326
|
+
// Every host runs this on pipes, so a TTY says nothing about whether the
|
|
327
|
+
// user will see the notice; a host with a verified user channel does.
|
|
328
|
+
// Through the host a notice is confirmed by a receipt naming its session,
|
|
329
|
+
// so without a session id there is nothing to confirm it by: say nothing.
|
|
330
|
+
const viaHost = envelopeField(raw, 'session_id') !== null && hostShowsUserNotice(envelopeField(raw, 'platform'));
|
|
264
331
|
const deps: NoticeDeps = {
|
|
265
332
|
packageRoot,
|
|
266
|
-
sessionId:
|
|
333
|
+
sessionId: envelopeField(raw, 'session_id'),
|
|
267
334
|
env: process.env,
|
|
268
|
-
isTty: Boolean(process.stderr.isTTY) || Boolean(process.stdout.isTTY),
|
|
335
|
+
isTty: viaHost || Boolean(process.stderr.isTTY) || Boolean(process.stdout.isTTY),
|
|
336
|
+
...(viaHost ? { notice: process.stdout, receipt: () => readNoticeReceipt(CONCERN) } : {}),
|
|
269
337
|
mode,
|
|
270
338
|
probeStatePath: user_global_paths.resolve_with_fallback(STATE_FILENAME),
|
|
271
339
|
noticeStatePath: user_global_paths.write_target(NOTICE_STATE_FILENAME),
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* recurses one level into nested objects; deeper trees fail render-time.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
import * as z from 'zod';
|
|
15
15
|
|
|
16
16
|
const ruleLoadingTier = z.enum(['minimal', 'balanced', 'full', 'custom']);
|
|
17
17
|
const disciplineProfile = z.enum(['auto', 'off', 'essential', 'full']);
|
|
@@ -42,7 +42,7 @@ export const settingsSchema = z.object({
|
|
|
42
42
|
id: profileId.default('developer').describe(
|
|
43
43
|
'Which experience you run — the audience identity that selects your default skill / command surface, README entry-path, and persona pre-selection (ADR-010, docs/contracts/profile-system.md). Six seed profiles: developer · content_creator · founder · agency · finance · ops. This is the first wizard question. In 6.0.0-A it records the choice only; pack-scoped surfacing (projection-time filtering, ADR-040) activates in 6.0.0-B behind a staged, opt-in rollout. Switch later with `agent-config use --profile=<id>`.',
|
|
44
44
|
),
|
|
45
|
-
}).
|
|
45
|
+
}).prefault({ id: 'developer' }),
|
|
46
46
|
projection: z.object({
|
|
47
47
|
mode: projectionMode.default('legacy-all').describe(
|
|
48
48
|
'Whether the per-tool projector writes EVERY artefact into the host-tool trees (.claude/ .cursor/ .windsurf/) or only the active profile + packs\' artefacts (ADR-040, docs/contracts/capability-packs.md). legacy-all = (default, non-breaking) project the full surface exactly as 5.x did. scoped = project only the active profile\'s packs unioned with the runtime.active_packs overlay, expanded over the requires graph — opt in with `agent-config use --profile=<id>`. A failed scoped projection restores the full tree.',
|
|
@@ -50,7 +50,7 @@ export const settingsSchema = z.object({
|
|
|
50
50
|
rule_packs: z.union([z.literal('auto'), z.array(z.string())]).default([]).describe(
|
|
51
51
|
'Scoping axis for the RULE layer beyond the derived workspace set, per pack ids (src/config/discovery/packs.yml). When set, a non-kernel rule also needs a packs frontmatter intersection to ship — e.g. deselecting frontend-design drops ui-audit-gate + design-fidelity. Opt-in, and an empty list is inactive; the workspace axis beside it is derived rather than configured (ADR-293 step 4.6). The literal "auto" derives the id list from the active-pack set (the same set the skill/command prune uses), so a domain safety floor stops shipping into installs that do not have the pack it guards; an explicit list stays supported and wins over the derivation.',
|
|
52
52
|
),
|
|
53
|
-
}).
|
|
53
|
+
}).prefault({ mode: 'legacy-all' }),
|
|
54
54
|
rule_loading_tier: ruleLoadingTier.default('balanced').describe(
|
|
55
55
|
'Master switch for which rule tiers load and how cautiously the agent spends tokens. minimal = only the 9 kernel rules (cheapest, fewest guardrails). balanced = kernel + tier-1 (recommended default). full = kernel + tier-1 + tier-2 (most guardrails, highest token cost). custom = roll your own in agents/overrides/. LEGACY: superseded by discipline_profile — when that key is set it wins (mapping: minimal→off, balanced→essential, full→full).',
|
|
56
56
|
),
|
|
@@ -78,7 +78,7 @@ export const settingsSchema = z.object({
|
|
|
78
78
|
strong: z.number().min(0).nullable().default(null).describe(
|
|
79
79
|
'USD ceiling for the strong model tier. null = no separate tier cap. The never-block-to-save-money relation this used to describe went with the archived budget-routing layer (docs/contracts/budget-routing.md); nothing routes between tiers today.',
|
|
80
80
|
),
|
|
81
|
-
}).
|
|
81
|
+
}).prefault({ cheap: null, medium: null, strong: null }),
|
|
82
82
|
}),
|
|
83
83
|
enforcement: enforcementMode.default('advisory').describe(
|
|
84
84
|
'What happens when a budget hits 100%. advisory = show a banner, keep working (default — never blocks an active task). hard-stop = refuse further model calls until the budget resets or you raise the ceiling.',
|
|
@@ -88,7 +88,7 @@ export const settingsSchema = z.object({
|
|
|
88
88
|
auto_switch: modelAutoSwitch.default('suggest').describe(
|
|
89
89
|
'Per-skill model auto-switch (ADR-035). Skills declare a vendor-neutral model_tier (lite/medium/high); the generator maps it to a native Claude model (high→opus, medium→sonnet, lite→haiku). suggest (default) = never emit a native Claude model: key; the model-recommendation rule names the tier as a one-question suggestion on every surface — your explicit /model choice is never silently overridden. auto = render a native Claude model: into lite/medium/high-tier skills so Claude Code switches automatically for that turn (reverts next prompt), and suggest on surfaces without a native override. off = inert, no native key and no suggestion.',
|
|
90
90
|
),
|
|
91
|
-
}).
|
|
91
|
+
}).prefault({ auto_switch: 'suggest' }),
|
|
92
92
|
personal: z.object({
|
|
93
93
|
ide: z.string().default('').describe(
|
|
94
94
|
'CLI binary your IDE registers (code, code-insiders, phpstorm, cursor, windsurf, idea, subl, …). Used by the file-editor skill to open edited files. Leave empty to disable IDE integration.',
|
|
@@ -133,7 +133,7 @@ export const settingsSchema = z.object({
|
|
|
133
133
|
update_strategy: gitUpdateStrategy.default('merge').describe(
|
|
134
134
|
'How a feature branch is brought up to date with its base. merge (default) = merge the base into the branch, as before this key existed. rebase = rebase the branch onto origin/<base> and publish with --force-with-lease, never a merge of the base into the branch. Rebase rewrites history, so the agent still asks for it unless the git-history-discipline rule already authorises the rewrite (the user asked this turn, an unrevoked standing instruction, or a pull --rebase the user started); this key decides which operation is proposed, never whether one is authorised.',
|
|
135
135
|
),
|
|
136
|
-
}).
|
|
136
|
+
}).prefault({}),
|
|
137
137
|
github: z.object({
|
|
138
138
|
pr_reply_method: replyMethod.default('create_review_comment').describe(
|
|
139
139
|
'How the agent replies to PR review comments. create_review_comment = post a new review comment (works on every GitHub plan). replies_endpoint = thread the reply under the original comment (needs the newer REST endpoint). auto = detect at runtime, prefer threaded replies when available.',
|
|
@@ -175,10 +175,10 @@ export const settingsSchema = z.object({
|
|
|
175
175
|
max_mcp_output_tokens: z.number().int().positive().nullable().default(null).describe(
|
|
176
176
|
'Token cap on a single MCP tool result the host places into a model request. null (default) = nothing written, host default 25000 applies. Same size-not-count caveat: it does not govern MCP connections, timeouts, or whether a tool runs.',
|
|
177
177
|
),
|
|
178
|
-
}).
|
|
178
|
+
}).prefault({}).describe(
|
|
179
179
|
'Request-size caps this package may write into the host env block. Both null by default. No traffic or auto-updater variable is expressible here, by construction — see the block comment.',
|
|
180
180
|
),
|
|
181
|
-
}).
|
|
181
|
+
}).prefault({}).describe(
|
|
182
182
|
'What this package may write into the host environment. ABSENT resolves to the all-null shape, which is the same thing as off — a settings file predating this section must not fail to parse, and "no section" and "both caps null" both mean write nothing.',
|
|
183
183
|
),
|
|
184
184
|
augment: z.object({
|
|
@@ -235,7 +235,7 @@ export const settingsSchema = z.object({
|
|
|
235
235
|
max_cost_per_rolling_7d_usd: z.number().min(0).nullable().default(null).describe(
|
|
236
236
|
'Rolling 7-day USD ceiling for class-1 gate execution, summed from the append-only receipt ledger at agents/runtime/state/gate-budget-ledger.jsonl. A run whose estimate would cross it renders instead of running. null (default) = no rolling ceiling, per ADR-279. A per-run cap alone bounds one mistake, not a week of them, which is why option (a) of b-gate-budget-preauth carries two numbers — but each cap now bounds on its own, so setting one and leaving the other null is supported.',
|
|
237
237
|
),
|
|
238
|
-
}).
|
|
238
|
+
}).prefault({ max_cost_per_run_usd: null, max_cost_per_rolling_7d_usd: null }),
|
|
239
239
|
}),
|
|
240
240
|
planning: z.object({
|
|
241
241
|
closure_pass: z.boolean().default(true).describe(
|
|
@@ -248,13 +248,13 @@ export const settingsSchema = z.object({
|
|
|
248
248
|
'Gate R2 — completion review at 100% roadmap completion / pre-PR. true (default) = a findings-before-fixes review by a fresh reviewer context must exist for the current diff hash (or an exact honest-null / skip declaration) before fix commits and PR creation, enforced by check_completion_review at pre-push + CI (CI authoritative; a crashed validator warns and allows). false = escape hatch, the validator skips.',
|
|
249
249
|
),
|
|
250
250
|
})
|
|
251
|
-
// `.
|
|
251
|
+
// `.prefault({})` is load-bearing, not cosmetic: without it the SECTION is
|
|
252
252
|
// required, so any settings payload that omits `planning` fails with
|
|
253
253
|
// `ZodError: planning Required` — which contradicts this gate family's own
|
|
254
254
|
// contract ("missing key = true" for all three) and reds every server
|
|
255
255
|
// test whose fixture predates the section. With the default, an absent
|
|
256
256
|
// section materialises as the three `true` leaves the contract promises.
|
|
257
|
-
.
|
|
257
|
+
.prefault({}),
|
|
258
258
|
execution: z.object({
|
|
259
259
|
fix_loop_max: z.number().int().min(1).default(10).describe(
|
|
260
260
|
'Consecutive failed fix attempts against one validation target before the escalation ladder reaches its terminal rung. Reaching the bound triggers a strategy change, never a question. Overridable globally, per project and per prompt.',
|
|
@@ -262,7 +262,7 @@ export const settingsSchema = z.object({
|
|
|
262
262
|
escalation: z.array(z.string()).default(['independent', 'council', 'team', 'owner_owned_check']).describe(
|
|
263
263
|
'The escalation rungs, in order, walked when the fix-loop bound is reached: independent (another session or a provider-diverse reviewer), council, team, then owner_owned_check — which asks whether the residue is owner-owned, not whether to ask the owner now.',
|
|
264
264
|
),
|
|
265
|
-
}).
|
|
265
|
+
}).prefault({}),
|
|
266
266
|
quality: z.object({
|
|
267
267
|
local_auto_run: z.boolean().default(false).describe(
|
|
268
268
|
'Run quality tools (linters, type-checks, formatters) and the local test suite autonomously after edits, IN CHAT. Off by default — in a chat turn a human is present and remote CI is the authoritative gate, so a local full-pipeline run duplicates it at wall-clock cost. The agent only runs a quality tool on an explicit ask, a concrete CI failure, or the new-gate carve-out. A mission is governed by local_auto_run_in_mission instead.',
|
|
@@ -289,10 +289,10 @@ export const settingsSchema = z.object({
|
|
|
289
289
|
'Length distance below which a value may be reconciled, in absolute CSS pixels. Ships null, same reasoning as color. GitHub Primer\'s ±1px spacing widening is recorded as an externally observed candidate in src/scripts/_lib/design_tolerance.ts and is deliberately not the shipped value; max(1px, 2%) was rejected for growing more permissive at large dimensions with no evidence that this is wanted.',
|
|
290
290
|
),
|
|
291
291
|
})
|
|
292
|
-
.
|
|
292
|
+
.prefault({ color: null, length: null }),
|
|
293
293
|
})
|
|
294
|
-
.
|
|
295
|
-
}).
|
|
294
|
+
.prefault({ enabled: false, tolerance: { color: null, length: null } }),
|
|
295
|
+
}).prefault({
|
|
296
296
|
fidelity_mode: 'strict',
|
|
297
297
|
approximation: { enabled: false, tolerance: { color: null, length: null } },
|
|
298
298
|
}),
|
|
@@ -300,7 +300,7 @@ export const settingsSchema = z.object({
|
|
|
300
300
|
consumer_index_paths: z.array(z.string()).default([]).describe(
|
|
301
301
|
"Extra project-relative paths where THIS project keeps a code-graph index another tool wrote. The built-in list is deliberately vendor-neutral (graph.json, code-graph.json, .code-graph/graph.json) and names no tool, so a tool that writes its index elsewhere is named here rather than waited for. A graph found this way LOADS and answers query and explain with every edge tagged read-from-elsewhere; the gate verbs (impact, untested, dead) refuse a graph made only of such edges and say so, because an empty answer from a gate must not read as 'nothing found' when it means 'nothing trusted'. Absolute paths and paths escaping the project root are ignored.",
|
|
302
302
|
),
|
|
303
|
-
}).
|
|
303
|
+
}).prefault({}),
|
|
304
304
|
screenshots: z.object({
|
|
305
305
|
identity_allowlist: z.array(z.string()).default([]).describe(
|
|
306
306
|
"Consumed by the doc-screenshot-hygiene rule and screenshot-hygiene skill. Public identity tokens SAFE to show unredacted in a documentation screenshot — the maintainer's own public handles plus well-known fake-data tokens. Not a general fake-data dictionary and not identity-resolution: everything not listed is treated as sensitive by default, and a public handle co-located with a real name does not whitelist the real name. Default [] = nothing auto-allowed.",
|
|
@@ -311,22 +311,22 @@ export const settingsSchema = z.object({
|
|
|
311
311
|
data_bearing_gate: z.enum(['on', 'off']).default('on').describe(
|
|
312
312
|
'Consumed by the doc-screenshot-hygiene rule. on (default) = a data-bearing screenshot embed is gated behind this-turn human confirmation; uncertain/unresolved regions redact-or-refuse, never ship-and-hope; illustrative/no-data screenshots may embed with a stated justification. off = no data-bearing gate (the anonymization taxonomy still applies).',
|
|
313
313
|
),
|
|
314
|
-
}).
|
|
314
|
+
}).prefault({ identity_allowlist: [], forbid_terminal_capture: true, data_bearing_gate: 'on' }),
|
|
315
315
|
verbosity: z.object({
|
|
316
316
|
offer_council_in_delivery: z.boolean().default(false).describe(
|
|
317
317
|
'Offer "run AI Council on this?" inside delivery commands (/feature-plan, /review-changes, /roadmap-create). Council commands themselves are unaffected.',
|
|
318
318
|
),
|
|
319
|
-
}).
|
|
319
|
+
}).prefault({ offer_council_in_delivery: false }),
|
|
320
320
|
code_style: z.object({
|
|
321
321
|
docblocks: z.enum(['minimal', 'full']).default('minimal').describe(
|
|
322
322
|
'Consumed by the code-comment-discipline rule. minimal (default) = no signature-mirroring docblocks; docblocks only for machine-relevant precision (generics, array shapes) or genuine why-context. full = the exported public surface of a library package may carry one-line summary docblocks; the redundancy ban still holds.',
|
|
323
323
|
),
|
|
324
|
-
}).
|
|
324
|
+
}).prefault({ docblocks: 'minimal' }),
|
|
325
325
|
reasoning: z.object({
|
|
326
326
|
enabled: z.boolean().default(true).describe(
|
|
327
327
|
'Master switch for the Reasoning Discipline Protocol (RDP). false = the whole layer is inert (zero overhead).',
|
|
328
328
|
),
|
|
329
|
-
}).
|
|
329
|
+
}).prefault({}),
|
|
330
330
|
subagents: z.object({
|
|
331
331
|
quota_arbitrage: z.boolean().default(true).describe(
|
|
332
332
|
'Prefer a separate quota-pool model for delegable sub-tasks where the host manifest reports one. Optional bonus only — identical behaviour (minus the quota win) where unsupported. Never load-bearing.',
|
|
@@ -335,7 +335,7 @@ export const settingsSchema = z.object({
|
|
|
335
335
|
lite: z.string().default('').describe('Model alias for lite-tier sub-tasks. Empty = the tier runtime default.'),
|
|
336
336
|
medium: z.string().default('').describe('Model alias for medium-tier sub-tasks. Empty = the tier runtime default.'),
|
|
337
337
|
high: z.string().default('').describe('Model alias for high-tier sub-tasks. Empty = the tier runtime default.'),
|
|
338
|
-
}).
|
|
338
|
+
}).prefault({}).describe(
|
|
339
339
|
'Per-tier model map for downshift routing. Each empty value uses the tier runtime default (no vendor model baked in).',
|
|
340
340
|
),
|
|
341
341
|
implementer_model: z.string().default('').describe(
|
|
@@ -362,7 +362,7 @@ export const settingsSchema = z.object({
|
|
|
362
362
|
cleanup: z.enum(['off', 'notify', 'auto']).default('notify').describe(
|
|
363
363
|
'What the agent may do about a worktree whose work is provably on the trunk (merged by merge commit, rebase or squash), that is clean, holds no ignored file a rebuild would not restore, and has no live session. notify (default, and what an absent key resolves to) says once a day how many exist and removes nothing; auto removes them right after /pr:merge and once a day on the first session start, without force, keeping local branches; off says nothing. The daily pass reads merged pull requests through your gh login (branch name and head commit only).',
|
|
364
364
|
),
|
|
365
|
-
}).
|
|
365
|
+
}).prefault({ cleanup: 'notify' }),
|
|
366
366
|
ai_team: z.object({
|
|
367
367
|
model: z.string().default('auto').describe(
|
|
368
368
|
"Model handed to the codex CLI. 'auto' (default) = pass no --model flag so the CLI's own default applies — tracks the subscription's current strongest model instead of pinning a stale ID. Any other value passes through verbatim as `--model <value>`.",
|
|
@@ -380,8 +380,8 @@ export const settingsSchema = z.object({
|
|
|
380
380
|
max_consecutive_blocks: z.number().int().min(1).default(3).describe(
|
|
381
381
|
'Circuit-breaker bound: after this many CONSECUTIVE BLOCK verdicts in one session, a visible notice is injected exactly once and the managed layer stops re-blocking — the user decides, never an infinite Claude↔Codex loop. An ALLOW verdict resets the counter. Positive integer.',
|
|
382
382
|
),
|
|
383
|
-
}).
|
|
384
|
-
}).
|
|
383
|
+
}).prefault({ managed: false, max_consecutive_blocks: 3 }),
|
|
384
|
+
}).prefault({ model: 'auto', allow_delegate: false, max_calls_per_day: 50, review_gate: { managed: false, max_consecutive_blocks: 3 } }),
|
|
385
385
|
emergency: z.object({
|
|
386
386
|
orchestration_halt: z.boolean().default(false).describe(
|
|
387
387
|
'The one audited incident switch over the always-on orchestration stack (subagents, council, team). NOT an activation gate: false (default) = the stack runs normally. true = halted; arming requires no ceremony. Disarming (returning to false) requires orchestration_halt_justification to be a non-empty string. Both transitions emit one telemetry line.',
|
|
@@ -389,7 +389,7 @@ export const settingsSchema = z.object({
|
|
|
389
389
|
orchestration_halt_justification: z.string().default('').describe(
|
|
390
390
|
'Required non-empty before orchestration_halt may return to false. Ignored while arming the halt.',
|
|
391
391
|
),
|
|
392
|
-
}).
|
|
392
|
+
}).prefault({ orchestration_halt: false, orchestration_halt_justification: '' }),
|
|
393
393
|
onboarding: z.object({
|
|
394
394
|
onboarded: z.boolean().default(false).describe(
|
|
395
395
|
'Set to true once the developer has completed `agent-config setup`. The onboarding-gate rule blocks the first turn of every chat until this is true. Toggle back to false to re-trigger the wizard.',
|
|
@@ -425,7 +425,7 @@ export const settingsSchema = z.object({
|
|
|
425
425
|
enabled: z.boolean().default(true).describe(
|
|
426
426
|
'Automatic retro after merged work (ADR-292, docs/contracts/retro-v1.md). true (default, owner directive 2026-10-12) = a merged PR and the first session of a new local day enqueue finished work; a deterministic triage picks what is worth one learning, and nothing behavior-changing is applied without a person. The one network read is the daily probe asking GitHub, through your own gh login, whether a pushed PR merged (repository and PR number only, three-second budget). false = no automatic path runs — no merge enqueue, no forge-merge marker, no daily probe and no network read; an explicit /retro still runs on request. Class C: an agent cannot set it; it cascades from the user-global file.',
|
|
427
427
|
),
|
|
428
|
-
}).
|
|
428
|
+
}).prefault({}),
|
|
429
429
|
memory: z.object({
|
|
430
430
|
redact_patterns: z.array(z.string()).default([]).describe(
|
|
431
431
|
'Regex patterns (one per line) that scrub matches from chat-history transcripts and memory before they hit disk. Use for secrets, customer names, internal URLs. Patterns are anchored and case-insensitive.',
|
|
@@ -445,15 +445,15 @@ export const settingsSchema = z.object({
|
|
|
445
445
|
allowed_tiers: z.array(z.string()).default(['public']).describe(
|
|
446
446
|
'Origin tiers auto-eligible to cross a project boundary. proprietary is manual-only regardless (the gate hard-codes it), so an in-house schema never auto-shares.',
|
|
447
447
|
),
|
|
448
|
-
}).
|
|
449
|
-
}).
|
|
448
|
+
}).prefault({}),
|
|
449
|
+
}).prefault({}),
|
|
450
450
|
egress: z.object({
|
|
451
451
|
placeholders: z.object({
|
|
452
452
|
mode: z.enum(['off', 'observe', 'protect', 'strict']).default('off').describe(
|
|
453
453
|
'Dispatcher egress stage (docs/contracts/egress-placeholders-v1.md). off (default) = nothing. observe = detect and count listed values in prompts and tool bodies, types and counts only, and change nothing a hook concern sees. protect = also give every recording and advisory concern a copy with each value replaced by a placeholder token, so local stores keep tokens; guards still see the original. strict = as protect until the host rewrite exists. No mode yet changes what reaches the model; the rule table holds credential rows and the validated identifier rows. Project layers only.',
|
|
454
454
|
),
|
|
455
|
-
}).
|
|
456
|
-
}).
|
|
455
|
+
}).prefault({}),
|
|
456
|
+
}).prefault({}),
|
|
457
457
|
hooks: z.object({
|
|
458
458
|
concern_budget: z.object({
|
|
459
459
|
tier1_concerns: z.array(z.string()).default([]).describe(
|
|
@@ -464,47 +464,47 @@ export const settingsSchema = z.object({
|
|
|
464
464
|
touched_file_quality: z.enum(['off', 'shadow', 'warn']).default('off').describe(
|
|
465
465
|
"Stop-slot pass that runs the project's OWN quality commands — the ones resolve_toolchain() already reports — over the files this turn edited. Default off. `shadow` records a per-run result and emits nothing; `warn` adds ONE advisory line of at most 200 bytes. Never blocks and never counts as verification: a command recorded here never reaches _lib/verification_command.ts, so a clean eslint run cannot let a turn claim it verified anything. A writing command runs only in its check form (pint --test) or not at all, and a command with no per-file form is skipped rather than run project-wide at every stop.",
|
|
466
466
|
),
|
|
467
|
-
}).
|
|
467
|
+
}).prefault({}),
|
|
468
468
|
injection_scan: z.object({
|
|
469
469
|
enabled: z.boolean().default(false).describe(
|
|
470
470
|
'PostToolUse prompt-injection scanner (road-to-security-pillar.md P3.2). Default off. When on, scans tool output (file reads, web fetches, MCP responses) for injection signatures and WARNS in context (never blocks). Runtime backstop on top of the always-on untrusted-input-defense rule; detection is probabilistic.',
|
|
471
471
|
),
|
|
472
|
-
}).
|
|
472
|
+
}).prefault({}),
|
|
473
473
|
runtime_journal: z.object({
|
|
474
474
|
enabled: z.boolean().default(false).describe(
|
|
475
475
|
'Runtime event journal (road-to-runtime-event-journal Phase 1). Default off. When on, one durable, episode-keyed record per dispatched hook event is appended to <git-common-dir>/agent-journal/journal.sqlite — a Class-A write that opens, writes and exits. It emits nothing to the model, never warns and never blocks: a locked database, a missing node:sqlite, a malformed envelope or a full disk all degrade to silence. Off rather than on because it CREATES a storage surface (a SQLite database under your .git/) rather than appending to a directory that already exists. The record type has no field able to hold free-form content — ids, an event name, a bounded capability identifier, repo-relative locators and two 12-hex digests; no prompt, no file body, no absolute path. Retention: a 30-day TTL anchored on episode close, plus explicit, time-bounded, human-only holds that themselves expire.',
|
|
476
476
|
),
|
|
477
|
-
}).
|
|
477
|
+
}).prefault({}),
|
|
478
478
|
rtk_wrap: z.object({
|
|
479
479
|
enabled: z.boolean().default(false).describe(
|
|
480
480
|
'PreToolUse RTK-wrap nudge (token-saving Phase 3). Default off. When on AND the binary on PATH is verified as Rust Token Killer (a live two-stage identity probe — not a self-reported flag, and never a colliding same-name binary), warns (never blocks) "re-run wrapped with rtk" before a single verbose CLI command (git/npm/cargo/docker/…) — upstream reports 60–90% output-token savings (their estimate). Skips completeness-critical / piped / compound commands and git diff. No-op when rtk is absent, unverified, or a different tool. Its own activation charge is one advisory line under the 1,024 B default concern cap in src/config/hook-token-budget.json; the per-fire distribution is UNMEASURED — stated rather than estimated, because the saving above is a third-party output-token claim and not a standing-payload reduction this package measured.',
|
|
481
481
|
),
|
|
482
|
-
}).
|
|
482
|
+
}).prefault({}),
|
|
483
483
|
design_slop: z.object({
|
|
484
484
|
enabled: z.boolean().default(false).describe(
|
|
485
485
|
'PreToolUse anti-slop nudge (road-to-anti-slop-detector Phase 3). Default off. When on, runs the lint_design_slop registry against about-to-be-written UI content and WARNS (never blocks) on P0/P1 aesthetic tells (side-stripe, gradient-text, magic z-index, …). Flags are rebuttable via DESIGN.md / design-slop-disable. Anti-loop: a file::rule signature surfaced 3x goes silent. Host-limited convenience layer; the universal gate is the lint_design_slop linter/CI.',
|
|
486
486
|
),
|
|
487
|
-
}).
|
|
487
|
+
}).prefault({}),
|
|
488
488
|
design_pass: z.object({
|
|
489
489
|
enabled: z.boolean().default(false).describe(
|
|
490
490
|
'PostToolUse + stop design pass (road-to-frontend-power E1.1/E1.2/E1.3). Default off. One concern on two slots: on post_tool_use a write to a UI surface delivers the design findings as context and never blocks; on stop the same pass runs over every UI file touched this session, deduped against what the post pass surfaced, and a P0 objective floor (contrast, font size, heading skip, focus) blocks with a continuation. P1-P3 never block. post_tool_use rather than pre_tool_use for two measured reasons: pre_tool_use is declared by three hosts and honoured by one while post_tool_use is declared by six, and _lib/ui_surface.ts is a path predicate, so a pre-write gate cannot fire on the first write of a new surface. A pass that could not fully run reports verification: degraded with a reason rather than passing silently. Does not replace design_slop: two design keys is the honest state until the tiering experiment has a number.',
|
|
491
491
|
),
|
|
492
|
-
}).
|
|
492
|
+
}).prefault({}),
|
|
493
493
|
ui_route_nudge: z.object({
|
|
494
494
|
enabled: z.boolean().default(false).describe(
|
|
495
495
|
'PreToolUse UI-route nudge (road-to-frontend-skill-application Phase 4). Default off. When on, a Write/Edit to a UI surface with no design consultation latched this session WARNS (never blocks) naming the route — run existing-ui-audit, then the fe-design loop. A read or search touching fe-design / existing-ui-audit / design-review / design-intelligence latches consultation and silences it for the session. Anti-loop: at most 2 nudges per session. It does not read the rules: the UI-surface decision comes from _lib/ui_surface.ts and no code parses rule frontmatter, so this runs parallel to the two UI rules rather than consuming their triggers, and a test keeps the sets from drifting. It is a nudge, so their enforced_by: none stays accurate.',
|
|
496
496
|
),
|
|
497
|
-
}).
|
|
497
|
+
}).prefault({}),
|
|
498
498
|
code_graph: z.object({
|
|
499
499
|
enabled: z.boolean().default(false).describe(
|
|
500
500
|
'PreToolUse code-graph nudge (ADR-124 Phase 4). Default off. When on AND a native code-graph cache or a consumer-shipped graph.json/SCIP index is present, warns once per session (never blocks) as the agent is about to Grep/Glob or Read a source file — query the graph first for who-calls/where-used/impact questions (or rebuild if stale, build if absent). Source G’s strict block-first-read mode is deliberately un-ported.',
|
|
501
501
|
),
|
|
502
|
-
}).
|
|
502
|
+
}).prefault({}),
|
|
503
503
|
suggestion_capture: z.object({
|
|
504
504
|
enabled: z.boolean().default(false).describe(
|
|
505
505
|
'Suggestion-block capture (road-to-suggestion-block-capture Phase 2). Default off. Two slots: `stop` reads `last_assistant_message` — a payload field, so no transcript file is read — and latches that the assistant turn carried a numbered-options block; `user_prompt_submit` CONSUMES that latch and classifies the answering turn as option_n / as_is / other / stale_block. Consume-once is the correctness guard: the latch is deleted on read, so a bare "1" three turns later meets no latch and classifies `other`, and a latch past its TTL or unparseable is `stale_block` rather than a guess. Writes COUNTS ONLY to agents/runtime/state/audit/suggestion-capture.jsonl — the record type has no field able to hold a prompt, an option label or a command name, and a test asserts the written key set against src/config/suggestion-capture.json. Off by default because this is an INSTRUMENT rather than a feature: it exists so a capture rate can be measured on a maintainer workspace over a fixed soak window, and there is nothing in it for a consumer to gain.',
|
|
506
506
|
),
|
|
507
|
-
}).
|
|
507
|
+
}).prefault({}),
|
|
508
508
|
// `turn_end_gate` is deliberately ABSENT. The stop-slot turn-end gate is
|
|
509
509
|
// always armed (2026-08-12) and has no settings surface: whether it
|
|
510
510
|
// fires is decided by each detector's own trigger conditions, not by a
|
|
@@ -544,12 +544,12 @@ export const settingsSchema = z.object({
|
|
|
544
544
|
kev: z.enum(['off', 'on']).default('on').describe(
|
|
545
545
|
'Whether a refresh reads the CISA known-exploited catalog. on (default) downloads the whole catalog and matches it locally, so nothing about the project is sent, and a finding in it is raised one tier in a runtime scope; off leaves tiers to the other sources. Runs only where egress is query.',
|
|
546
546
|
),
|
|
547
|
-
}).
|
|
547
|
+
}).prefault({ mode: 'notify', egress: 'query', kev: 'on' }),
|
|
548
548
|
update: z.object({
|
|
549
549
|
mode: z.enum(['off', 'notify', 'ask', 'auto']).default('notify').describe(
|
|
550
550
|
'What the agent may do about a newer release. notify tells you and installs nothing (the default, and what an absent key resolves to); ask behaves as notify until the ring-0 exit ships a prompt; auto installs a staged release at a session start when every gate passes and no other session is live; off says nothing at all. Read from the user-global layer only — a repository cannot set how your global install updates itself.',
|
|
551
551
|
),
|
|
552
|
-
}).
|
|
552
|
+
}).prefault({ mode: 'notify' }),
|
|
553
553
|
legal_review_prep: z.object({
|
|
554
554
|
acknowledged: z.boolean().default(false).describe(
|
|
555
555
|
'I understand the legal-review-prep pack provides templates and general information ONLY — it is NOT legal advice, creates no attorney-client relationship, and never replaces a licensed lawyer. Individual cases require an attorney. The pack stays inactive until this is checked.',
|
|
@@ -557,7 +557,7 @@ export const settingsSchema = z.object({
|
|
|
557
557
|
require_council: z.boolean().default(true).describe(
|
|
558
558
|
'Gate legal work-product behind a multi-model AI-council / deep-research pass (defense-in-depth: documented multi-stage review + audit trail; fail-closed when no council is configured). Leave on for the safest posture. Turning it off lets single-model legal output through — not recommended for a high-risk pack.',
|
|
559
559
|
),
|
|
560
|
-
}).
|
|
560
|
+
}).prefault({ acknowledged: false, require_council: true }),
|
|
561
561
|
});
|
|
562
562
|
|
|
563
563
|
export type Settings = z.infer<typeof settingsSchema>;
|
|
@@ -82,7 +82,7 @@ function unwrapRef(root: JsonSchemaNode): JsonSchemaNode {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* Flatten a `
|
|
85
|
+
* Flatten a `toJsonSchema7` output into dotted leaf entries. Objects with
|
|
86
86
|
* `properties` recurse; everything else (scalars, enums, arrays, free-form
|
|
87
87
|
* maps) is a leaf. Deterministic key order (sorted) so snapshots diff
|
|
88
88
|
* cleanly in git and across runs.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* the same Zod schema. ESLint forbids Node-only imports in this tree.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
import * as z from 'zod';
|
|
15
15
|
|
|
16
16
|
/** Hard cap on the optional `notes` block. Keeps the file loadable into every reply. */
|
|
17
17
|
export const USER_IDENTITY_NOTES_MAX_CHARS = 8_000;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Zod schema as a draft-07 JSON Schema, in the envelope the settings surface,
|
|
3
|
+
* the generated settings reference and the UI form renderer read:
|
|
4
|
+
* `{ $ref: '#/definitions/<name>', definitions: { <name>: … }, $schema }`.
|
|
5
|
+
*
|
|
6
|
+
* Zod 4 converts natively (`z.toJSONSchema`); this replaces `zod-to-json-schema`,
|
|
7
|
+
* which only understands Zod 3. `io: 'input'` keeps a defaulted field optional,
|
|
8
|
+
* as the old converter did, because a settings file may omit it.
|
|
9
|
+
*/
|
|
10
|
+
import * as z from 'zod';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The places the native output differs from what consumers were built on: an
|
|
14
|
+
* object without `additionalProperties` (the old converter closed every
|
|
15
|
+
* object), an `enum` without a `type` (it named the primitive type), and an
|
|
16
|
+
* integer carrying the safe-integer bounds Zod 4 adds to `.int()`, which says
|
|
17
|
+
* nothing a reader of a settings schema can use.
|
|
18
|
+
*/
|
|
19
|
+
function normalise(node: unknown): void {
|
|
20
|
+
if (Array.isArray(node)) {
|
|
21
|
+
for (const n of node) normalise(n);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (typeof node !== 'object' || node === null) return;
|
|
25
|
+
const o = node as Record<string, unknown>;
|
|
26
|
+
if (o['properties'] !== undefined && o['additionalProperties'] === undefined) o['additionalProperties'] = false;
|
|
27
|
+
if (Array.isArray(o['enum']) && o['type'] === undefined) {
|
|
28
|
+
const kinds = new Set((o['enum'] as unknown[]).map((v) => typeof v));
|
|
29
|
+
if (kinds.size === 1 && (kinds.has('string') || kinds.has('number') || kinds.has('boolean'))) o['type'] = [...kinds][0];
|
|
30
|
+
}
|
|
31
|
+
if (o['maximum'] === Number.MAX_SAFE_INTEGER) delete o['maximum'];
|
|
32
|
+
if (o['minimum'] === Number.MIN_SAFE_INTEGER) delete o['minimum'];
|
|
33
|
+
for (const v of Object.values(o)) normalise(v);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function toJsonSchema7(schema: z.ZodType, name: string): Record<string, unknown> {
|
|
37
|
+
const body = z.toJSONSchema(schema, { target: 'draft-7', io: 'input', unrepresentable: 'any' }) as Record<string, unknown>;
|
|
38
|
+
normalise(body);
|
|
39
|
+
const { $schema: _drop, ...definition } = body;
|
|
40
|
+
return { $ref: `#/definitions/${name}`, definitions: { [name]: definition }, $schema: 'http://json-schema.org/draft-07/schema#' };
|
|
41
|
+
}
|
|
@@ -10,9 +10,9 @@ with no manual parser install (ADR-259).
|
|
|
10
10
|
|
|
11
11
|
`tree-sitter-wasms` carries 36 grammars (49 MiB on disk). Depending on it at
|
|
12
12
|
runtime would hand every consumer all 36, which is the option ADR-259 §
|
|
13
|
-
Alternatives rejects on exactly that measurement. Only the
|
|
13
|
+
Alternatives rejects on exactly that measurement. Only the eight grammars below
|
|
14
14
|
are loadable by the engine — `GRAMMAR_WASM` in `src/scripts/code_graph/types.ts`
|
|
15
|
-
has
|
|
15
|
+
has eight entries — so the other 28 would be payload with no reader. Vue needs no
|
|
16
16
|
grammar of its own: the extractor blanks a component's template and parses its
|
|
17
17
|
`<script>` with the TypeScript grammar.
|
|
18
18
|
|
|
@@ -29,8 +29,10 @@ the tarball; the loader reads them from disk.
|
|
|
29
29
|
| python | `tree-sitter-python.wasm` | `9056d0fb0c337810d019fae350e8167786119da98f0f282aceae7ab89ee8253b` |
|
|
30
30
|
| go | `tree-sitter-go.wasm` | `9963ca89b616eaf04b08a43bc1fb0f07b85395bec313330851f1f1ead2f755b6` |
|
|
31
31
|
| java | `tree-sitter-java.wasm` | `637aac4415fb39a211a4f4292d63c66b5ce9c32fa2cd35464af4f681d91b9a1f` |
|
|
32
|
+
| csharp | `tree-sitter-c_sharp.wasm` | `6266a7e32d68a3459104d994dc848df15d5672b0ea8e86d327274b694f8e6991` |
|
|
33
|
+
| ruby | `tree-sitter-ruby.wasm` | `93a5022855314cdb45458c7bb026a24a0ebc3a5ff6439e542e881f14dfa13a39` |
|
|
32
34
|
|
|
33
|
-
All
|
|
35
|
+
All eight are built against **grammar ABI 14** (`EXPECTED_GRAMMAR_ABI` in
|
|
34
36
|
`src/scripts/code_graph/types.ts`). The loader asserts the ABI on every load, so
|
|
35
37
|
a drifted grammar fails loudly rather than parsing wrongly.
|
|
36
38
|
|
|
@@ -38,7 +40,7 @@ a drifted grammar fails loudly rather than parsing wrongly.
|
|
|
38
40
|
|
|
39
41
|
Copied byte-for-byte from `tree-sitter-wasms@0.1.13`
|
|
40
42
|
(<https://github.com/Gregoor/tree-sitter-wasms>, Unlicense), which compiles them
|
|
41
|
-
from the upstream `tree-sitter/tree-sitter-{php,typescript,javascript}` grammars
|
|
43
|
+
from the upstream `tree-sitter/tree-sitter-{php,typescript,javascript,python,go,java,c-sharp,ruby}` grammars
|
|
42
44
|
(MIT). The compiled artifacts are derivatives of the MIT grammar sources, so
|
|
43
45
|
they are annotated MIT in `REUSE.toml` and credited in `CREDITS.md`.
|
|
44
46
|
|
|
@@ -73,7 +75,7 @@ The pair is ABI-coupled and must move together:
|
|
|
73
75
|
|
|
74
76
|
```bash
|
|
75
77
|
npm i -D web-tree-sitter@<v> tree-sitter-wasms@<v>
|
|
76
|
-
for g in php typescript javascript python go java; do
|
|
78
|
+
for g in php typescript javascript python go java c_sharp ruby; do
|
|
77
79
|
cp node_modules/tree-sitter-wasms/out/tree-sitter-$g.wasm src/vendor/grammars/
|
|
78
80
|
done
|
|
79
81
|
shasum -a 256 src/vendor/grammars/*.wasm # update the table above
|
|
@@ -81,7 +83,7 @@ shasum -a 256 src/vendor/grammars/*.wasm # update the table above
|
|
|
81
83
|
|
|
82
84
|
Then update, in the same change:
|
|
83
85
|
|
|
84
|
-
- the
|
|
86
|
+
- the eight entries in `src/config/packed-binary-manifest.json` (they are
|
|
85
87
|
path-, sha256- and size-bound, so any refresh invalidates them by design).
|
|
86
88
|
This used to name `BINARY_PAYLOAD_EXCEPTIONS` in `src/scripts/check_pack_size.ts`,
|
|
87
89
|
a constant that no longer exists — `grep -rn BINARY_PAYLOAD_EXCEPTIONS` returned
|
|
Binary file
|
|
Binary file
|