@event4u/agent-config 10.0.0 → 10.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +172 -5
- package/README.md +1 -1
- package/dist/agent-src/commands/roadmap/next.md +38 -0
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +22 -1
- package/dist/agent-src/contexts/contracts/frugality-charter.md +1 -1
- package/dist/agent-src/rules/context-hygiene.md +39 -0
- package/dist/agent-src/rules/token-budget-discipline.md +3 -2
- package/dist/agent-src/rules/token-efficiency.md +22 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +17 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +5 -0
- package/dist/agent-src/skills/ai-council/SKILL.md +28 -6
- package/dist/agent-src/skills/check-refs/SKILL.md +5 -0
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/lint-skills/SKILL.md +5 -0
- package/dist/agent-src/skills/md-language-check/SKILL.md +5 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/token-optimizer/SKILL.md +5 -1
- package/dist/agent-src/templates/agent-settings.md +12 -28
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +2 -2
- package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-3JUTT26I.js → chunk-6ENSXSBW.js} +1 -1
- package/dist/cli-delegate/{chunk-TVGXH7VA.js → chunk-6LOPHJLD.js} +1 -1
- package/dist/cli-delegate/{chunk-XT2YTJCZ.js → chunk-AYKQGIBG.js} +4 -0
- package/dist/cli-delegate/{chunk-HJSKTMIF.js → chunk-BV3WR67D.js} +3 -19
- package/dist/cli-delegate/{chunk-HTIU3W56.js → chunk-BWMXLD4B.js} +6 -2
- package/dist/cli-delegate/{chunk-2H5SE7DU.js → chunk-E4RUZKXB.js} +165 -38
- package/dist/cli-delegate/{chunk-XTC5ARB6.js → chunk-IR3IVRVC.js} +7 -2
- package/dist/cli-delegate/{chunk-LRXJK6GN.js → chunk-JMQZ7MM5.js} +1 -1
- package/dist/cli-delegate/{chunk-NX5AXSLD.js → chunk-X3VOARZD.js} +3 -3
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +881 -204
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +6 -5
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +44 -9
- package/dist/cli-delegate/cmd_settings_get.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +15 -15
- 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 +88 -20
- package/dist/install/install.mjs +9 -21
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1359 -492
- package/dist/scripts/_lib/agent_settings.js +25 -2
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/_lib/compile_time_toggles.js +4 -3
- package/dist/scripts/_lib/compile_time_toggles.js.map +1 -1
- package/dist/server/io/yamlIO.js +0 -2
- package/dist/server/io/yamlIO.js.map +1 -1
- package/dist/server/schemas/settings.js +3 -9
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/settingsClasses.js +64 -2
- package/dist/shared/settingsClasses.js.map +1 -1
- package/dist/ui/assets/{index-B5qGYOJo.js → index-5fXOHoVh.js} +2 -2
- package/dist/ui/assets/index-5fXOHoVh.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +307 -145
- package/docs/guidelines/agent-infra/layered-settings.md +1 -2
- package/docs/guidelines/agent-infra/symptom-driven-harvest-loop.md +104 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +17 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -1
- package/src/config/agent-settings.template.yml +12 -30
- package/src/config/gate-violation-baselines.json +9 -3
- package/src/config/profiles/balanced.ini +0 -2
- package/src/config/profiles/full.ini +1 -3
- package/src/config/profiles/minimal.ini +0 -2
- package/src/scripts/_cli/cmd_session_recycle.ts +66 -9
- package/src/scripts/_cli/handoff_sessions.ts +3 -2
- package/src/scripts/_lib/agent_settings.ts +25 -2
- package/src/scripts/_lib/bench_telegraph.ts +1 -1
- package/src/scripts/_lib/cc_transcript.ts +25 -0
- package/src/scripts/_lib/compile_time_toggles.ts +4 -3
- package/src/scripts/_lib/untrusted_content.test.ts +175 -0
- package/src/scripts/_lib/untrusted_content.ts +184 -0
- package/src/scripts/ai_council/clients.ts +228 -33
- package/src/scripts/cache_realization_report.ts +3 -2
- package/src/scripts/check_rule_projection_integrity.ts +3 -1
- package/src/scripts/conformance_scan.ts +282 -9
- package/src/scripts/cost/track.mjs +10 -4
- package/src/scripts/council_cli.ts +33 -3
- package/src/scripts/external_sources_denylist.json +3 -1
- package/src/scripts/hook_manifest.yaml +17 -6
- package/src/scripts/hooks/block_unauthorized_git.ts +54 -8
- package/src/scripts/hooks/delegation_nudge_hook.ts +33 -8
- package/src/scripts/hooks/evidence_independence.ts +61 -9
- package/src/scripts/hooks/session_eol_hook.ts +123 -3
- package/src/scripts/hooks/turn_end_gate_hook.ts +31 -0
- package/src/scripts/lint_settings_classes.ts +273 -11
- package/src/scripts/lint_symptom_intake.ts +241 -0
- package/src/scripts/report_consultation_rate.ts +2 -1
- package/src/scripts/report_skill_activation.ts +6 -4
- package/src/scripts/report_skill_obligation_violations.ts +2 -1
- package/src/scripts/schemas/skill.schema.json +42 -0
- package/src/scripts/skill_linter.ts +57 -2
- package/src/server/io/yamlIO.ts +0 -2
- package/src/server/schemas/settings.ts +3 -19
- package/src/shared/settingsClasses.ts +93 -2
- package/dist/ui/assets/index-B5qGYOJo.js.map +0 -1
|
@@ -33,6 +33,48 @@
|
|
|
33
33
|
"maxLength": 500,
|
|
34
34
|
"description": "Agent-Skills spec field (optional): machine-readable declaration of environment requirements — system packages, network access, product/host constraints. Mirrors the public spec so media/doc skills can state requirements outside prose."
|
|
35
35
|
},
|
|
36
|
+
"runtime_requires": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"description": "Structured runtime prerequisites, in the form `doctor`/`preflight` can actually probe. The free-text `compatibility` field above states requirements for a human reader; this states them for a machine, and the two coexist deliberately — `compatibility` mirrors the public Agent-Skills spec and is not replaced here. Mandatory for an external `execution.handler` (shell/node/php), which cannot run without declared dependencies; meaningless for `internal`, which by definition needs no external binary. NOT named `requires`: that key is taken by ADR-015 pack-dependency edges (a list of pack ids, validated in build_discovery_manifest.ts), and reusing it makes every skill carrying one unassignable in the discovery manifest.",
|
|
40
|
+
"properties": {
|
|
41
|
+
"bins": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"items": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"minLength": 1
|
|
46
|
+
},
|
|
47
|
+
"description": "Executables that must resolve on PATH (e.g. `python3`, `curl`). Probed by name, never by version — a version constraint belongs in the skill body where it can explain itself."
|
|
48
|
+
},
|
|
49
|
+
"env": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"pattern": "^[A-Z][A-Z0-9_]*$"
|
|
54
|
+
},
|
|
55
|
+
"description": "Environment variable NAMES that must be set. Names only — a value here would be a credential in a tracked file (secret-vcs-guard)."
|
|
56
|
+
},
|
|
57
|
+
"primary_env": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"pattern": "^[A-Z][A-Z0-9_]*$",
|
|
60
|
+
"description": "The one variable from `env` whose absence makes the skill unusable rather than degraded, so a doctor report can distinguish a hard block from a partial one. Must also appear in `env`. HALF-ENFORCED, stated plainly: the `dependencies` keyword below requires `env` to be present whenever `primary_env` is, which is the part JSON Schema can express. That the named variable is a MEMBER of `env` is NOT checked anywhere — the linter reads frontmatter by regex, and a fragile pattern on a blocking rule is worse than an honest gap. Zero skills use the field today; the membership check belongs in the doctor wiring (Phase 1, gated on S0.3), where the value is parsed rather than matched."
|
|
61
|
+
},
|
|
62
|
+
"network": {
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"minLength": 1
|
|
67
|
+
},
|
|
68
|
+
"description": "Egress hostnames the skill contacts, checked against the egress allowlist. An empty array is a positive claim of no network access and is NOT the same as omitting the key."
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"primary_env": [
|
|
73
|
+
"env"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"$comment": "road-to-skill-ecosystem-executable-payloads Phase 1 (defect D2): `compatibility` is free text used by 2 skills, so cmd_doctor.ts / cmd_preflight.ts had nothing structured to verify. Named `runtime_requires` because `requires` is reserved for ADR-015 pack edges. The `dependencies` keyword closes the half of the primary_env invariant a schema can express; R2 review finding 1 flagged the fully-unenforced version as the same ADR-127 shape this roadmap is closing."
|
|
77
|
+
},
|
|
36
78
|
"provenance": {
|
|
37
79
|
"type": "string",
|
|
38
80
|
"enum": [
|
|
@@ -230,6 +230,30 @@ const KERNEL_RULE_IDS = new Set([
|
|
|
230
230
|
export const VALID_EXECUTION_TYPES = new Set(['manual', 'assisted', 'automated']);
|
|
231
231
|
export const VALID_EXECUTION_HANDLERS = new Set(['none', 'shell', 'php', 'node', 'internal']);
|
|
232
232
|
const VALID_EXECUTION_SAFETY_MODES = new Set(['strict']);
|
|
233
|
+
/**
|
|
234
|
+
* Handlers that shell out of this process and therefore depend on something the
|
|
235
|
+
* host must already have. `internal` is deliberately absent: it runs in-process
|
|
236
|
+
* and by definition needs no external binary, so demanding a `requires` block
|
|
237
|
+
* from it would produce a pro-forma declaration rather than a checkable fact.
|
|
238
|
+
*/
|
|
239
|
+
const EXTERNAL_EXECUTION_HANDLERS = new Set(['shell', 'php', 'node']);
|
|
240
|
+
/**
|
|
241
|
+
* Top-level `runtime_requires:` declaration (sibling of `execution:`, not a field
|
|
242
|
+
* inside it). Deliberately NOT `requires:` — that key is ADR-015 pack-dependency
|
|
243
|
+
* edges (a list of pack ids), and a skill carrying an object there is rejected by
|
|
244
|
+
* `build_discovery_manifest.ts` as unassignable.
|
|
245
|
+
*
|
|
246
|
+
* Matches block style (`runtime_requires:` then indented keys) AND flow style
|
|
247
|
+
* (`runtime_requires: {bins: [...]}`). The block-only version was an R2 review
|
|
248
|
+
* finding: flow style is valid YAML and valid against the schema, so erroring on
|
|
249
|
+
* it told the author to add a declaration they already had, with no suppression
|
|
250
|
+
* path. The neighbouring `^execution:\s*$` is block-only by house convention, but
|
|
251
|
+
* its consequence is a silent skip rather than a hard error.
|
|
252
|
+
*
|
|
253
|
+
* `[ \t]*` rather than `\s*`: `\s` matches a newline, so the looser form could
|
|
254
|
+
* span lines and accept a bare key whose value sits somewhere below it.
|
|
255
|
+
*/
|
|
256
|
+
const RUNTIME_REQUIRES_BLOCK_PATTERN = /^runtime_requires:[ \t]*(\{.*)?$/m;
|
|
233
257
|
const VALID_EXECUTION_FIELDS = new Set([
|
|
234
258
|
'type',
|
|
235
259
|
'handler',
|
|
@@ -1264,7 +1288,14 @@ function lint_wing4_boundaries(text: string): Issue[] {
|
|
|
1264
1288
|
return issues;
|
|
1265
1289
|
}
|
|
1266
1290
|
|
|
1267
|
-
|
|
1291
|
+
/**
|
|
1292
|
+
* `frontmatter` is required, not defaulted. An R2 review finding: with
|
|
1293
|
+
* `frontmatter = ''` an omitted argument makes the runtime-requires check
|
|
1294
|
+
* FABRICATE its error rather than skip it, which is the wrong failure direction
|
|
1295
|
+
* for an optional parameter — a future caller that forgets it gets a confident
|
|
1296
|
+
* error about a declaration it never looked for.
|
|
1297
|
+
*/
|
|
1298
|
+
function lint_execution_metadata(execution: ExecutionBlock, frontmatter: string): Issue[] {
|
|
1268
1299
|
const issues: Issue[] = [];
|
|
1269
1300
|
|
|
1270
1301
|
const execType = execution.type;
|
|
@@ -1374,6 +1405,30 @@ function lint_execution_metadata(execution: ExecutionBlock): Issue[] {
|
|
|
1374
1405
|
}
|
|
1375
1406
|
}
|
|
1376
1407
|
|
|
1408
|
+
// An external handler that actually declares a command depends on binaries or
|
|
1409
|
+
// env the host may not have; without a `runtime_requires` block,
|
|
1410
|
+
// `doctor`/`preflight` have nothing to probe and the failure surfaces mid-run
|
|
1411
|
+
// instead. Gated on a declared `command:` on purpose — a `handler: shell` skill
|
|
1412
|
+
// with no command is advisory prose, and demanding requirements from it would be
|
|
1413
|
+
// a pro-forma field.
|
|
1414
|
+
if (
|
|
1415
|
+
typeof handler === 'string' &&
|
|
1416
|
+
EXTERNAL_EXECUTION_HANDLERS.has(handler) &&
|
|
1417
|
+
Array.isArray(command) &&
|
|
1418
|
+
command.length > 0 &&
|
|
1419
|
+
!RUNTIME_REQUIRES_BLOCK_PATTERN.test(frontmatter)
|
|
1420
|
+
) {
|
|
1421
|
+
issues.push(
|
|
1422
|
+
new Issue(
|
|
1423
|
+
'error',
|
|
1424
|
+
'missing_runtime_requires',
|
|
1425
|
+
`execution.handler '${handler}' declares a command but the skill has no top-level ` +
|
|
1426
|
+
"'runtime_requires:' block — declare bins/env/network so doctor and preflight can " +
|
|
1427
|
+
'verify the runtime before the command runs',
|
|
1428
|
+
),
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1377
1432
|
const unknown = Object.keys(execution).filter((k) => !VALID_EXECUTION_FIELDS.has(k));
|
|
1378
1433
|
for (const field of unknown.sort()) {
|
|
1379
1434
|
issues.push(
|
|
@@ -1530,7 +1585,7 @@ export function lint_skill(p: string, text: string, absPath: string | null = nul
|
|
|
1530
1585
|
|
|
1531
1586
|
const execution = parseExecutionBlock(frontmatter);
|
|
1532
1587
|
if (execution !== null) {
|
|
1533
|
-
issues.push(...lint_execution_metadata(execution));
|
|
1588
|
+
issues.push(...lint_execution_metadata(execution, frontmatter));
|
|
1534
1589
|
}
|
|
1535
1590
|
|
|
1536
1591
|
const tierMatch = TIER_PATTERN.exec(frontmatter);
|
package/src/server/io/yamlIO.ts
CHANGED
|
@@ -144,8 +144,6 @@ export const TEMPLATE_PLACEHOLDER_DEFAULTS: Readonly<Record<string, string>> = {
|
|
|
144
144
|
__DISCIPLINE_PROFILE__: 'auto',
|
|
145
145
|
__USER_TYPE__: '',
|
|
146
146
|
__CHAT_HISTORY_FREQUENCY__: 'per_turn',
|
|
147
|
-
__CHAT_HISTORY_MAX_SIZE_KB__: '2048',
|
|
148
|
-
__CHAT_HISTORY_ON_OVERFLOW__: 'rotate',
|
|
149
147
|
};
|
|
150
148
|
|
|
151
149
|
/** Replace every known template placeholder with its default value. */
|
|
@@ -21,7 +21,6 @@ const userType = z.enum(['', 'consultant', 'creator', 'developer', 'finance', 'f
|
|
|
21
21
|
const profileId = z.enum(['developer', 'content_creator', 'founder', 'agency', 'finance', 'ops']);
|
|
22
22
|
const accessStyle = z.enum(['getters_setters', 'get_attribute', 'magic_properties']);
|
|
23
23
|
const chatFreq = z.enum(['per_turn', 'per_phase', 'per_tool']);
|
|
24
|
-
const chatOverflow = z.enum(['rotate', 'condense']);
|
|
25
24
|
const qualityCadence = z.enum(['end_of_roadmap', 'per_phase', 'per_step']);
|
|
26
25
|
const regenCadence = z.enum(['per_step', 'every_5_steps', 'phase_boundary']);
|
|
27
26
|
const worktreeMode = z.enum(['off', 'on', 'ask']);
|
|
@@ -169,12 +168,6 @@ export const settingsSchema = z.object({
|
|
|
169
168
|
frequency: chatFreq.default('per_turn').describe(
|
|
170
169
|
'How often the chat-history writer flushes to disk. per_turn = after every user / agent exchange (default, lowest data loss on crash). per_phase = at phase boundaries (cheaper I/O). per_tool = after every tool call (highest fidelity, noisiest log).',
|
|
171
170
|
),
|
|
172
|
-
max_size_kb: z.number().int().min(0).default(2048).describe(
|
|
173
|
-
'Maximum size (KB) of the active chat-history file before chat_history.on_overflow kicks in. Set 0 to disable rotation / condensation entirely (file grows forever).',
|
|
174
|
-
),
|
|
175
|
-
on_overflow: chatOverflow.default('rotate').describe(
|
|
176
|
-
'What happens when chat_history.max_size_kb is hit. rotate = move the current log aside and start fresh (default). condense = telegraph-condense the oldest entries in place to keep recent context.',
|
|
177
|
-
),
|
|
178
171
|
text_limits: z.object({
|
|
179
172
|
user: z.number().int().min(0).default(0).describe(
|
|
180
173
|
'Per-message character cap for user inputs in the chat-history log. 0 = log verbatim (default). Raise above 0 only if your prompts contain large pasted artefacts you do not want stored.',
|
|
@@ -231,9 +224,6 @@ export const settingsSchema = z.object({
|
|
|
231
224
|
local_auto_run: z.boolean().default(false).describe(
|
|
232
225
|
'Run quality tools (linters, type-checks, formatters) and the local test suite autonomously after edits. Off by default — the agent never runs quality tools proactively and does not ask; the user runs them manually (e.g. /quality-fix) and remote CI is the authoritative gate. The agent only runs a quality tool on an explicit ask, a concrete CI failure, or the new-gate carve-out. Turn on to restore autonomous pipeline runs.',
|
|
233
226
|
),
|
|
234
|
-
wait_for_remote_ci: z.boolean().default(false).describe(
|
|
235
|
-
'After pushing a branch, poll the remote CI provider (GitHub Actions, GitLab CI) and surface failures inline. Off by default — useful when local CI does not cover everything the remote pipeline runs.',
|
|
236
|
-
),
|
|
237
227
|
}),
|
|
238
228
|
design: z.object({
|
|
239
229
|
fidelity_mode: fidelityMode.default('strict').describe(
|
|
@@ -258,12 +248,9 @@ export const settingsSchema = z.object({
|
|
|
258
248
|
}).default({ identity_allowlist: [], forbid_terminal_capture: true, data_bearing_gate: 'on' }),
|
|
259
249
|
telegraph: z.object({
|
|
260
250
|
speak: z.boolean().default(false).describe(
|
|
261
|
-
'Whether the telegraph-speak rule ships at all. false (default) = DORMANT: compile_router omits the rule from dist/router.json entirely, so its body never reaches a host. This
|
|
251
|
+
'Whether the telegraph-speak rule ships at all. false (default) = DORMANT: compile_router omits the rule from dist/router.json entirely, so its body never reaches a host. This is the only lever that stops the cost. Set true only after an output-side bench clears the kill-criterion bar (docs/adrs/telegraph/0002).',
|
|
262
252
|
),
|
|
263
|
-
|
|
264
|
-
"Where telegraph condensation applies WHEN speak is true. off (default) = agent reply prose is exempt. compile_router never reads this key: with speak false the rule does not ship, so this setting has no effect on its own. Quote the value in YAML — bare `off` parses as a boolean under YAML 1.1 and is rejected.",
|
|
265
|
-
),
|
|
266
|
-
}).default({ speak: false, speak_scope: 'off' }),
|
|
253
|
+
}).default({ speak: false }),
|
|
267
254
|
tokens: z.object({
|
|
268
255
|
rich_skills: richSkillsMode.default('on').describe(
|
|
269
256
|
'Whether skills marked token_budget_class: rich may load in full (exempt from telegraph-speak + thin-projector trimming), consumed by the token-budget-discipline rule. on = allowed (default); off = fall back to standard condensed behavior; ask = surface an estimated token delta (tokens, not dollars) and ask once per session before loading.',
|
|
@@ -573,13 +560,10 @@ export const settingsSchema = z.object({
|
|
|
573
560
|
acknowledged: z.boolean().default(false).describe(
|
|
574
561
|
'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.',
|
|
575
562
|
),
|
|
576
|
-
consented_at: z.string().default('').describe(
|
|
577
|
-
'ISO timestamp recorded when the legal-review-prep acknowledgment was given. Set automatically by the setup wizard; leave empty otherwise.',
|
|
578
|
-
),
|
|
579
563
|
require_council: z.boolean().default(true).describe(
|
|
580
564
|
'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.',
|
|
581
565
|
),
|
|
582
|
-
}).default({ acknowledged: false,
|
|
566
|
+
}).default({ acknowledged: false, require_council: true }),
|
|
583
567
|
});
|
|
584
568
|
|
|
585
569
|
export type Settings = z.infer<typeof settingsSchema>;
|
|
@@ -24,16 +24,61 @@ export function isSettingsClass(value: string): value is SettingsClass {
|
|
|
24
24
|
return (SETTINGS_CLASSES as readonly string[]).includes(value);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* The closed disposition vocabulary — the axis orthogonal to A/B/C.
|
|
29
|
+
*
|
|
30
|
+
* A/B/C answers *who may write a key*. This answers *whether the key should
|
|
31
|
+
* exist at all*, which is a different question with a different action per
|
|
32
|
+
* value. A row outside the vocabulary is a defect, not a new disposition.
|
|
33
|
+
*/
|
|
34
|
+
export const SETTINGS_DISPOSITIONS = ['derivable', 'un-inferrable', 'consent', 'policy'] as const;
|
|
35
|
+
|
|
36
|
+
export type SettingsDisposition = (typeof SETTINGS_DISPOSITIONS)[number];
|
|
37
|
+
|
|
38
|
+
export function isSettingsDisposition(value: string): value is SettingsDisposition {
|
|
39
|
+
return (SETTINGS_DISPOSITIONS as readonly string[]).includes(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
/** One parsed row of the contract's key table. */
|
|
28
43
|
export interface SettingsClassRow {
|
|
29
44
|
/** Dotted leaf path, e.g. `personal.autonomy`. */
|
|
30
45
|
key: string;
|
|
31
46
|
/** Raw class cell — NOT yet validated, so the lint can report a bad one. */
|
|
32
47
|
cls: string;
|
|
48
|
+
/**
|
|
49
|
+
* Raw disposition cell, or `null` when the row has no fifth column at all.
|
|
50
|
+
*
|
|
51
|
+
* NOT validated here, for the same reason `cls` is not: the lint has to be
|
|
52
|
+
* able to report a bad value, and a parser that silently drops it would
|
|
53
|
+
* make an unclassified key indistinguishable from a mis-spelled one.
|
|
54
|
+
*
|
|
55
|
+
* `derivable` rows carry their replacement after an em dash — the clause
|
|
56
|
+
* that makes the label falsifiable rather than a synonym for "inconvenient
|
|
57
|
+
* to keep". `dispositionOf` returns the label, `replacementOf` the clause.
|
|
58
|
+
*/
|
|
59
|
+
disposition: string | null;
|
|
33
60
|
/** 1-indexed line in the contract, so a finding points at something. */
|
|
34
61
|
line: number;
|
|
35
62
|
}
|
|
36
63
|
|
|
64
|
+
/** `derivable — the detector's own trigger condition` → `derivable`. */
|
|
65
|
+
export function dispositionOf(cell: string | null): string | null {
|
|
66
|
+
if (cell === null) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const [label] = cell.split('—');
|
|
70
|
+
return (label ?? '').trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** `derivable — the detector's own trigger condition` → the clause after the dash. */
|
|
74
|
+
export function replacementOf(cell: string | null): string {
|
|
75
|
+
if (cell === null) {
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
const dash = cell.indexOf('—');
|
|
79
|
+
return dash === -1 ? '' : cell.slice(dash + 1).trim();
|
|
80
|
+
}
|
|
81
|
+
|
|
37
82
|
/** What the contract's own `## Counts` table claims about itself. */
|
|
38
83
|
export interface DeclaredClassCounts {
|
|
39
84
|
A: number | null;
|
|
@@ -51,11 +96,20 @@ export interface DeclaredClassCounts {
|
|
|
51
96
|
*/
|
|
52
97
|
const ROW_RE = /^\|\s*`([^`]+)`\s*\|\s*([^|]*?)\s*\|/;
|
|
53
98
|
|
|
99
|
+
/**
|
|
100
|
+
* The disposition is read as the FIFTH cell, appended after `Why`, so the four
|
|
101
|
+
* cells every existing consumer indexes keep their positions. A row that
|
|
102
|
+
* predates the axis simply has no fifth cell and parses to `null` — an absent
|
|
103
|
+
* disposition, which the lint reports, rather than a shifted `Default`.
|
|
104
|
+
*/
|
|
105
|
+
const DISPOSITION_RE = /^\|\s*`[^`]+`\s*\|[^|]*\|[^|]*\|[^|]*\|\s*([^|]*?)\s*\|/;
|
|
106
|
+
|
|
54
107
|
export function parseSettingsClassRows(markdown: string): SettingsClassRow[] {
|
|
55
108
|
const rows: SettingsClassRow[] = [];
|
|
56
109
|
const lines = markdown.split('\n');
|
|
57
110
|
for (let i = 0; i < lines.length; i++) {
|
|
58
|
-
const
|
|
111
|
+
const line = lines[i] ?? '';
|
|
112
|
+
const m = ROW_RE.exec(line);
|
|
59
113
|
if (m === null) {
|
|
60
114
|
continue;
|
|
61
115
|
}
|
|
@@ -64,7 +118,9 @@ export function parseSettingsClassRows(markdown: string): SettingsClassRow[] {
|
|
|
64
118
|
if (!/^[A-Za-z]$/.test(cls)) {
|
|
65
119
|
continue;
|
|
66
120
|
}
|
|
67
|
-
|
|
121
|
+
const d = DISPOSITION_RE.exec(line);
|
|
122
|
+
const raw = d?.[1] ?? '';
|
|
123
|
+
rows.push({ key, cls, disposition: raw === '' ? null : raw, line: i + 1 });
|
|
68
124
|
}
|
|
69
125
|
return rows;
|
|
70
126
|
}
|
|
@@ -84,6 +140,41 @@ export function parseDeclaredClassCounts(markdown: string): DeclaredClassCounts
|
|
|
84
140
|
};
|
|
85
141
|
}
|
|
86
142
|
|
|
143
|
+
/** What the contract's own `## Dispositions` table claims about itself. */
|
|
144
|
+
export interface DeclaredDispositionCounts {
|
|
145
|
+
derivable: number | null;
|
|
146
|
+
'un-inferrable': number | null;
|
|
147
|
+
consent: number | null;
|
|
148
|
+
policy: number | null;
|
|
149
|
+
total: number | null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** `| derivable | 12 |` and `| **Total** | **140** |` under `## Dispositions`. */
|
|
153
|
+
export function parseDeclaredDispositionCounts(markdown: string): DeclaredDispositionCounts {
|
|
154
|
+
// Scoped to the section, so the class Counts table's own **Total** row —
|
|
155
|
+
// identical in shape and earlier in the file — cannot be read as this one.
|
|
156
|
+
const start = markdown.indexOf('\n## Dispositions');
|
|
157
|
+
const section = start === -1 ? '' : markdown.slice(start + 1);
|
|
158
|
+
const end = section.indexOf('\n## ', 1);
|
|
159
|
+
const scoped = end === -1 ? section : section.slice(0, end);
|
|
160
|
+
// The value cell is read with optional `**` so the bolded `**Total**` row
|
|
161
|
+
// and the plain per-label rows go through one path — a second regex for the
|
|
162
|
+
// total is a second thing to get wrong.
|
|
163
|
+
const read = (label: string): number | null => {
|
|
164
|
+
const re = new RegExp(String.raw`^\|\s*` + label + String.raw`\s*\|\s*\**(\d+)\**\s*\|`, 'm');
|
|
165
|
+
const m = re.exec(scoped);
|
|
166
|
+
const raw = m?.[1];
|
|
167
|
+
return raw === undefined ? null : Number.parseInt(raw, 10);
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
derivable: read('derivable'),
|
|
171
|
+
'un-inferrable': read('un-inferrable'),
|
|
172
|
+
consent: read('consent'),
|
|
173
|
+
policy: read('policy'),
|
|
174
|
+
total: read(String.raw`\*\*Total\*\*`),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
87
178
|
/**
|
|
88
179
|
* Dotted key → class, for the rows whose class is valid.
|
|
89
180
|
*
|