@event4u/agent-config 14.17.0 → 14.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +207 -77
- package/README.md +72 -150
- package/dist/agent-src/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +2 -2
- package/dist/agent-src/commands/pr/merge.md +35 -44
- package/dist/agent-src/commands/review/changes.md +19 -19
- package/dist/agent-src/commands/review.md +2 -2
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +30 -3
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/git-history-discipline.md +2 -2
- package/dist/agent-src/rules/guidelines.md +1 -1
- package/dist/agent-src/rules/session-canary.md +2 -2
- package/dist/agent-src/rules/token-efficiency.md +1 -1
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +2 -0
- package/dist/agent-src/skills/adversarial-review/SKILL.md +0 -8
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +27 -1
- package/dist/agent-src/skills/canvas-design/SKILL.md +1 -1
- package/dist/agent-src/skills/code-intelligence/SKILL.md +60 -12
- package/dist/agent-src/skills/complexity-first-planning/SKILL.md +27 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +1 -1
- package/dist/agent-src/skills/design-intelligence/data/app-interface.csv +1 -1
- package/dist/agent-src/skills/design-intelligence/data/motion.csv +17 -17
- package/dist/agent-src/skills/design-intelligence/data/ux-guidelines.csv +2 -2
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +17 -0
- package/dist/agent-src/skills/design-intelligence/references/design-rules-checklist.md +4 -4
- package/dist/agent-src/skills/design-review/SKILL.md +3 -1
- package/dist/agent-src/skills/design-variations/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +1 -1
- package/dist/agent-src/skills/fe-design/SKILL.md +32 -5
- package/dist/agent-src/skills/fe-design/references/craft-floor.md +7 -0
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +47 -2
- package/dist/agent-src/skills/feature-planning/SKILL.md +34 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +37 -12
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +176 -0
- package/dist/agent-src/skills/laravel-mail/SKILL.md +44 -0
- package/dist/agent-src/skills/laravel-scheduling/SKILL.md +3 -1
- package/dist/agent-src/skills/project-analyzer/SKILL.md +0 -6
- package/dist/agent-src/skills/react-native-setup/SKILL.md +3 -3
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/sequential-thinking/SKILL.md +18 -8
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +40 -0
- package/dist/agent-src/skills/ui-apply-generic/SKILL.md +1 -1
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +1 -1
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +29 -2
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +53 -8
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +3 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/commands/recordTriggerEval.js +2 -2
- package/dist/cli/main.js +1 -1
- package/dist/cli-delegate/{chunk-AKJGUQNO.js → chunk-GZTMRYCN.js} +405 -341
- package/dist/cli-delegate/{chunk-7RXIKF62.js → chunk-IDHUQIXP.js} +283 -132
- package/dist/cli-delegate/cmd_adr_effective.js +318 -81
- package/dist/cli-delegate/cmd_conformance.js +749 -669
- package/dist/cli-delegate/cmd_doctor.js +1 -1
- package/dist/cli-delegate/cmd_export.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +1 -1
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +1 -1
- package/dist/cli-delegate/cmd_uninstall.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +39 -38
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +2764 -2439
- package/dist/install/install.mjs +588 -527
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1381 -1138
- package/dist/scripts/_lib/model_tier.js +1 -1
- package/dist/shared/settingsConsent.js +1 -1
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/docs/guidelines/design-antipatterns.md +87 -64
- package/package.json +1 -1
- package/src/agent-src/README.md +1 -1
- package/src/config/ci-local-parity.yml +6 -0
- package/src/config/conformance-claim-baseline.json +9 -0
- package/src/config/gate-coverage.yml +170 -0
- package/src/config/gate-violation-baselines.json +4 -4
- package/src/config/release-gate-locality.yml +141 -0
- package/src/scripts/_cli/cmd_doctor.ts +2 -2
- package/src/scripts/_cli/cmd_handoff.ts +1 -1
- package/src/scripts/_cli/cmd_update.ts +1 -1
- package/src/scripts/_cli/cmd_versions.ts +1 -1
- package/src/scripts/_lib/adr_frontmatter.ts +44 -0
- package/src/scripts/_lib/consequence_objects.ts +152 -0
- package/src/scripts/_lib/exec_evidence.ts +7 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +2 -2
- package/src/scripts/_lib/host_permission_checks.ts +251 -0
- package/src/scripts/_lib/model_tier.ts +1 -1
- package/src/scripts/_lib/obligation_frequency.ts +2 -2
- package/src/scripts/_lib/release_highlights.ts +265 -1
- package/src/scripts/_lib/release_material.ts +129 -0
- package/src/scripts/_lib/repeated_run.ts +77 -0
- package/src/scripts/_lib/runtime_journal.ts +26 -3
- package/src/scripts/_lib/runtime_wiring_checks.ts +12 -1
- package/src/scripts/_lib/session_register.ts +11 -6
- package/src/scripts/_lib/sqlite_schema.ts +22 -0
- package/src/scripts/_lib/surface.ts +86 -0
- package/src/scripts/_lib/tamper_vocabulary.ts +242 -0
- package/src/scripts/_lib/ui_authority.ts +116 -0
- package/src/scripts/_lib/unified_diff.ts +121 -0
- package/src/scripts/adr/regenerate_index.ts +54 -1
- package/src/scripts/adr_cite_check.ts +71 -15
- package/src/scripts/ai_council/clients.ts +1 -1
- package/src/scripts/autonomy_friction_corpus.ts +229 -0
- package/src/scripts/build_proof.ts +15 -2
- package/src/scripts/check_beta_review_markers.ts +141 -17
- package/src/scripts/check_enforcement_coverage.ts +1 -1
- package/src/scripts/check_enforcement_denominator.ts +121 -11
- package/src/scripts/check_finding_dispositions.ts +239 -6
- package/src/scripts/check_gate_coverage.ts +96 -0
- package/src/scripts/check_installed_hooks_fresh.ts +381 -0
- package/src/scripts/check_release_highlights.ts +99 -14
- package/src/scripts/check_skill_activation_claim.ts +179 -0
- package/src/scripts/check_suppression_hygiene.ts +11 -0
- package/src/scripts/check_trigger_evals.ts +1 -1
- package/src/scripts/code_graph/build.ts +230 -33
- package/src/scripts/code_graph/extract.ts +168 -33
- package/src/scripts/code_graph/types.ts +37 -2
- package/src/scripts/code_graph/validate.ts +12 -1
- package/src/scripts/conformance_scan.ts +45 -4
- package/src/scripts/detect_silent_catch.ts +403 -0
- package/src/scripts/detect_verification_tampering.ts +538 -0
- package/src/scripts/explain_run.ts +1 -1
- package/src/scripts/generate_capability_matrix.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +228 -18
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +47 -30
- package/src/scripts/hooks/augment-dispatcher.sh +1 -1
- package/src/scripts/hooks/block_no_verify.ts +3 -3
- package/src/scripts/hooks/category_a.ts +291 -0
- package/src/scripts/hooks/cline-dispatcher.sh +1 -1
- package/src/scripts/hooks/concern_registry.ts +2 -2
- package/src/scripts/hooks/cursor-dispatcher.sh +11 -5
- package/src/scripts/hooks/dispatch_hook.ts +17 -22
- package/src/scripts/hooks/evidence_independence.ts +21 -2
- package/src/scripts/hooks/{block_unauthorized_git.ts → git_command_classifier.ts} +175 -364
- package/src/scripts/hooks/host_lowering.ts +215 -0
- package/src/scripts/hooks/host_lowering.yaml +185 -0
- package/src/scripts/hooks/host_semantics.ts +105 -37
- package/src/scripts/hooks/journal_record_hook.ts +10 -0
- package/src/scripts/hooks/permission_gate.ts +117 -0
- package/src/scripts/hooks/push_settle_hook.ts +250 -0
- package/src/scripts/hooks/ship_diff_volume_hook.ts +1 -1
- package/src/scripts/hooks_status.ts +12 -10
- package/src/scripts/install-hooks.sh +134 -1
- package/src/scripts/install.ts +53 -82
- package/src/scripts/install_anthropic_key.sh +2 -2
- package/src/scripts/language_mirror_hook.ts +27 -5
- package/src/scripts/lint_design_antipattern_parity.ts +80 -0
- package/src/scripts/lint_evidence_artifacts.ts +45 -1
- package/src/scripts/lint_hook_manifest.ts +87 -3
- package/src/scripts/lint_motion_authority_drift.ts +731 -0
- package/src/scripts/lint_readme_size.ts +158 -12
- package/src/scripts/lint_reasoning_fixtures.ts +216 -0
- package/src/scripts/lint_skill_trigger_contradiction.ts +161 -0
- package/src/scripts/measure_sibling_naming.ts +150 -0
- package/src/scripts/redact_hook_capture.ts +1 -1
- package/src/scripts/release.ts +31 -143
- package/src/scripts/release_drill.ts +71 -1
- package/src/scripts/release_publication.ts +243 -3
- package/src/scripts/release_verify.ts +220 -0
- package/src/scripts/report_skill_activation.ts +319 -7
- package/src/scripts/schemas/review-findings.schema.json +6 -1
- package/src/scripts/schemas/skill.schema.json +12 -2
- package/src/scripts/self_review_gate.ts +10 -1
- package/src/scripts/session_canary_hook.ts +1 -1
- package/src/scripts/session_register_hook.ts +4 -3
- package/src/scripts/trigger_eval_grandfather.json +5 -1
- package/src/scripts/validate_agent_settings.ts +1 -1
- package/src/shared/settingsConsent.ts +1 -1
|
@@ -288,3 +288,132 @@ function _first_diff(a: string, b: string): string {
|
|
|
288
288
|
}
|
|
289
289
|
return 'surfaces differ only in normalization-invisible content';
|
|
290
290
|
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* The marker the governance-versus-product response line carries.
|
|
294
|
+
*
|
|
295
|
+
* Relocated here on 2026-09-05 from `check_release_highlights.ts`, where it was
|
|
296
|
+
* defined next to the refusal that reads it. That placement is why 14.17.0
|
|
297
|
+
* failed: the gate owned the marker, so the **generator** could not write the
|
|
298
|
+
* line without importing a gate, and therefore did not write it at all. The
|
|
299
|
+
* obligation was then discoverable only after `gh pr create` had already spent
|
|
300
|
+
* a branch, a PR and a CI run — the exact cost `guard_release_branch_push` was
|
|
301
|
+
* added to avoid for the sibling obligation two releases earlier.
|
|
302
|
+
*
|
|
303
|
+
* This module has no imports on purpose (see `CURATED_HEAD_INSTRUCTION`), which
|
|
304
|
+
* is what lets the writer, the local push guard and the CI gate all reach one
|
|
305
|
+
* definition instead of three copies.
|
|
306
|
+
*/
|
|
307
|
+
export const MIX_RESPONSE_MARKER = '**Governance mix:**';
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The sentinel that marks an EMITTED-BUT-UNANSWERED response.
|
|
311
|
+
*
|
|
312
|
+
* A generator that emits the measured level alone would satisfy the gate's
|
|
313
|
+
* length floor (`MIX_RESPONSE_MIN_CHARS`, 40) with a number the machine
|
|
314
|
+
* produced, turning a written-answer obligation into a formality the tool
|
|
315
|
+
* discharges for itself. That is a smuggled auto-approval, not a fix.
|
|
316
|
+
*
|
|
317
|
+
* So the writer emits the level AND this sentinel, and every gate refuses the
|
|
318
|
+
* sentinel — the same discipline `CURATED_HEAD_INSTRUCTION` and
|
|
319
|
+
* `DERIVED_MARKER` already follow. The net effect is only that the obligation
|
|
320
|
+
* moves from CI to the moment the section is written, where the fix is an edit
|
|
321
|
+
* to a file already open in the working tree.
|
|
322
|
+
*/
|
|
323
|
+
export const MIX_RESPONSE_PLACEHOLDER = '<the consumer work>';
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Every placeholder token the writer emits, so none of them can ship.
|
|
327
|
+
*
|
|
328
|
+
* The block carries two, and refusing only the first would leave
|
|
329
|
+
* `<roadmap or issue>` publishable — `CHANGELOG.md` is in `package.json`
|
|
330
|
+
* `files`, so that is the same "the generator's own scaffolding reached npm"
|
|
331
|
+
* failure `CURATED_HEAD_INSTRUCTION` exists for, one token to the right.
|
|
332
|
+
*
|
|
333
|
+
* A named list rather than a shape match over angle brackets, for the reason
|
|
334
|
+
* that constant already states: a pattern such as `/<[a-z ]+>/` rejects
|
|
335
|
+
* unrelated legitimate prose and misses a reworded placeholder. Adding a token
|
|
336
|
+
* to `render_mix_response` means adding it here.
|
|
337
|
+
*/
|
|
338
|
+
export const MIX_RESPONSE_PLACEHOLDERS: readonly string[] = [
|
|
339
|
+
MIX_RESPONSE_PLACEHOLDER,
|
|
340
|
+
'<roadmap or issue>',
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Render the response block the writer emits when the obligation triggers.
|
|
345
|
+
*
|
|
346
|
+
* Two blockquote lines, immediately under the curated head and outside it, per
|
|
347
|
+
* `docs/contracts/CHANGELOG-conventions.md` § Governance-versus-product
|
|
348
|
+
* response — the head's ten-line cap is for product lines, and a sixth label
|
|
349
|
+
* would make every historical section retroactively incomplete.
|
|
350
|
+
*/
|
|
351
|
+
export function render_mix_response(level: string): string[] {
|
|
352
|
+
return [
|
|
353
|
+
`> ${MIX_RESPONSE_MARKER} ${level}.`,
|
|
354
|
+
`> ${PROMISE_PHRASE} ${MIX_RESPONSE_PLACEHOLDER}, tracked in <roadmap or issue>.`,
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* The words that turn a governance response into a PROMISE about a later cycle.
|
|
360
|
+
*
|
|
361
|
+
* Named rather than inlined because two sides now read it: the writer emits it,
|
|
362
|
+
* and the next release reads the previous section for it to decide whether a
|
|
363
|
+
* read-back is owed. A promise nothing reads back costs nothing to restate, so
|
|
364
|
+
* it can be restated for as many cycles as there are cycles — the defect this
|
|
365
|
+
* constant exists to make addressable.
|
|
366
|
+
*/
|
|
367
|
+
export const PROMISE_PHRASE = 'Next cycle ships';
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* The marker under which a release head answers the PREVIOUS head's promise.
|
|
371
|
+
*
|
|
372
|
+
* A named sentinel, on the same discipline as `MIX_RESPONSE_MARKER` and
|
|
373
|
+
* `DERIVED_MARKER`: a shape match over prose would accept a sentence that
|
|
374
|
+
* happens to mention last cycle and reject a reworded answer.
|
|
375
|
+
*/
|
|
376
|
+
export const PROMISE_READBACK_MARKER = '**Previous cycle:**';
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* The three outcomes a read-back may report, and the reason there are exactly
|
|
380
|
+
* three.
|
|
381
|
+
*
|
|
382
|
+
* `shipped` and `did not ship` are the honest halves of the fact. `withdrawn`
|
|
383
|
+
* exists because a promise the project decided not to keep is a legitimate
|
|
384
|
+
* third state — but only WITH a reason, which is why the answer also has to
|
|
385
|
+
* clear a written-length floor rather than being one of these three words on
|
|
386
|
+
* its own. Without the third state the mechanism would push an author toward
|
|
387
|
+
* restating a dead promise as still-outstanding, which is the failure it is
|
|
388
|
+
* built to stop.
|
|
389
|
+
*/
|
|
390
|
+
export const PROMISE_OUTCOMES: readonly string[] = ['shipped', 'did not ship', 'withdrawn'];
|
|
391
|
+
|
|
392
|
+
/** Every `## X.Y.Z` release version in the file, in document order (newest first). */
|
|
393
|
+
export function changelog_versions(text: string): string[] {
|
|
394
|
+
const out: string[] = [];
|
|
395
|
+
for (const m of text.matchAll(/^##\s+\[?(\d+\.\d+\.\d+)\b/gmu)) {
|
|
396
|
+
out.push(m[1] as string);
|
|
397
|
+
}
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* The release published immediately before `version`, or null when there is none.
|
|
403
|
+
*
|
|
404
|
+
* Read from the CHANGELOG rather than from git tags on purpose: the promise
|
|
405
|
+
* being read back is a property of the SECTION, and a tag can exist for a
|
|
406
|
+
* version whose section was archived into `docs/archive/` under an era split —
|
|
407
|
+
* in which case there is no previous promise reachable in this file and the
|
|
408
|
+
* honest answer is that none is owed.
|
|
409
|
+
*
|
|
410
|
+
* `version` itself need not be present: a section still under authoring is
|
|
411
|
+
* named `[Unreleased]`, so the caller passes the version it is releasing and
|
|
412
|
+
* gets the newest RELEASED version that is not it.
|
|
413
|
+
*/
|
|
414
|
+
export function previous_changelog_version(text: string, version: string): string | null {
|
|
415
|
+
const versions = changelog_versions(text);
|
|
416
|
+
const idx = versions.indexOf(version);
|
|
417
|
+
if (idx !== -1) return versions[idx + 1] ?? null;
|
|
418
|
+
return versions[0] ?? null;
|
|
419
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The repeated-run evidence mode and the `flaky` outcome.
|
|
3
|
+
*
|
|
4
|
+
* `docs/contracts/evidence-artifact-types.md` § The second axis is the
|
|
5
|
+
* contract; this is the executable half, so "a run that passes 4 of 5 times is
|
|
6
|
+
* recorded as flaky rather than as passing" is a thing a test can assert rather
|
|
7
|
+
* than a sentence in a document.
|
|
8
|
+
*
|
|
9
|
+
* Two rules carry the whole design, both from the AI council of 2026-09-04
|
|
10
|
+
* (2/2 convergent):
|
|
11
|
+
*
|
|
12
|
+
* 1. **`n >= 2`.** `repeated:1` is `single-run` wearing extra syntax, and
|
|
13
|
+
* admitting it would let one run be presented as a repeat.
|
|
14
|
+
* 2. **Unanimity, no threshold.** All pass → pass. All fail → fail. Anything
|
|
15
|
+
* else → flaky. A 4-of-5 pass is precisely the case this exists to stop
|
|
16
|
+
* being called a pass, so a majority rule would defeat the purpose.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type ExecutionMode = { kind: 'single-run' } | { kind: 'repeated'; n: number };
|
|
20
|
+
|
|
21
|
+
export type RepeatedOutcome = 'pass' | 'fail' | 'flaky';
|
|
22
|
+
|
|
23
|
+
/** The marker, when an artifact declares one. `single-run` is never written. */
|
|
24
|
+
export const EVIDENCE_MODE_RE = /<!--\s*evidence-mode:\s*(single-run|repeated:(\d+))\s*-->/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read the mode an artifact declares.
|
|
28
|
+
*
|
|
29
|
+
* Absent → `single-run`, which is the default that keeps this axis from being
|
|
30
|
+
* a migration. A malformed `repeated:<n>` with `n < 2` is rejected rather than
|
|
31
|
+
* rounded to `single-run`: a declaration that does not parse must not read to
|
|
32
|
+
* its author as accepted.
|
|
33
|
+
*/
|
|
34
|
+
export function parseExecutionMode(text: string): ExecutionMode | { kind: 'invalid'; reason: string } {
|
|
35
|
+
const m = EVIDENCE_MODE_RE.exec(text);
|
|
36
|
+
if (m === null) return { kind: 'single-run' };
|
|
37
|
+
if (m[1] === 'single-run') return { kind: 'single-run' };
|
|
38
|
+
const n = Number(m[2]);
|
|
39
|
+
if (!Number.isInteger(n) || n < 2) {
|
|
40
|
+
return {
|
|
41
|
+
kind: 'invalid',
|
|
42
|
+
reason: `repeated:${String(n)} declares fewer than 2 runs; repeated:1 is single-run with extra syntax`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return { kind: 'repeated', n };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Aggregate `n` retained per-run verdicts.
|
|
50
|
+
*
|
|
51
|
+
* Takes the RUNS, never a summary count — an artifact that kept only
|
|
52
|
+
* "3 of 5 passed" has recorded a number, not evidence, and this signature is
|
|
53
|
+
* where that requirement is enforced rather than requested.
|
|
54
|
+
*/
|
|
55
|
+
export function aggregateRepeatedRuns(runs: readonly boolean[]): RepeatedOutcome {
|
|
56
|
+
if (runs.length < 2) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`aggregateRepeatedRuns needs at least 2 retained runs, got ${String(runs.length)}; ` +
|
|
59
|
+
'a single run is `single-run`, not a repeat',
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (runs.every((r) => r)) return 'pass';
|
|
63
|
+
if (runs.every((r) => !r)) return 'fail';
|
|
64
|
+
return 'flaky';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** One line for the artifact, so `flaky` is reported as itself. */
|
|
68
|
+
export function describeRepeatedRun(runs: readonly boolean[]): string {
|
|
69
|
+
const outcome = aggregateRepeatedRuns(runs);
|
|
70
|
+
const passed = runs.filter((r) => r).length;
|
|
71
|
+
const line = `evidence-mode: repeated:${String(runs.length)} · outcome: ${outcome} · ${String(passed)}/${String(runs.length)} passed`;
|
|
72
|
+
if (outcome !== 'flaky') return line;
|
|
73
|
+
return (
|
|
74
|
+
`${line} — the runs did not agree, so this is neither a pass nor a fail. ` +
|
|
75
|
+
'Recording it as either would be the silent green or the silent red this outcome exists to prevent.'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -174,6 +174,8 @@ import type { DatabaseSync } from 'node:sqlite';
|
|
|
174
174
|
import { current_branch, git_common_dir, git_dir } from './git_common_dir.js';
|
|
175
175
|
import type { TerminalState } from './outcome_envelope.js';
|
|
176
176
|
import { readRunTerminalState, RUN_TERMINAL_STATES } from './outcome_vocabularies.js';
|
|
177
|
+
import { addColumnIfMissing } from './sqlite_schema.js';
|
|
178
|
+
import { readSurface, type Surface } from './surface.js';
|
|
177
179
|
import {
|
|
178
180
|
isSqliteAvailableSync,
|
|
179
181
|
loadSqliteSync,
|
|
@@ -408,6 +410,14 @@ export interface JournalEvent {
|
|
|
408
410
|
at: string;
|
|
409
411
|
/** Bounded identifier: the hook or command name. Never free text. */
|
|
410
412
|
capability: string;
|
|
413
|
+
/**
|
|
414
|
+
* IDE / CLI / cloud dimension of the dispatch. Closed vocabulary, and
|
|
415
|
+
* almost always `unknown`: no host observed here sends a distinguishing
|
|
416
|
+
* marker and the detector refuses to infer one. Recorded anyway because
|
|
417
|
+
* "did this come from the surface the lattice assumed" has no answer at
|
|
418
|
+
* all while the field is absent.
|
|
419
|
+
*/
|
|
420
|
+
surface: Surface;
|
|
411
421
|
/** Imported from `outcome_envelope.ts`; null until an episode terminates. */
|
|
412
422
|
terminal_state: TerminalState | null;
|
|
413
423
|
/** Repo-relative locator for the return. Never an absolute path. */
|
|
@@ -447,6 +457,7 @@ export const JOURNAL_RECORD_KEYS = Object.freeze([
|
|
|
447
457
|
'worktree_id',
|
|
448
458
|
'at',
|
|
449
459
|
'capability',
|
|
460
|
+
'surface',
|
|
450
461
|
'terminal_state',
|
|
451
462
|
'return_ref',
|
|
452
463
|
'verification_ref',
|
|
@@ -759,6 +770,7 @@ function createSchema(db: DatabaseSync): void {
|
|
|
759
770
|
worktree_id TEXT NOT NULL,
|
|
760
771
|
at TEXT NOT NULL,
|
|
761
772
|
capability TEXT NOT NULL,
|
|
773
|
+
surface TEXT NOT NULL DEFAULT 'unknown',
|
|
762
774
|
terminal_state TEXT,
|
|
763
775
|
return_ref TEXT,
|
|
764
776
|
verification_ref TEXT,
|
|
@@ -767,6 +779,9 @@ function createSchema(db: DatabaseSync): void {
|
|
|
767
779
|
amends_seq INTEGER
|
|
768
780
|
)`,
|
|
769
781
|
);
|
|
782
|
+
// A pre-existing row takes the default, which is the honest value: that
|
|
783
|
+
// dispatch was never asked what surface it came from.
|
|
784
|
+
addColumnIfMissing(db, 'journal_event', 'surface', "TEXT NOT NULL DEFAULT 'unknown'");
|
|
770
785
|
db.exec('CREATE INDEX IF NOT EXISTS idx_event_episode ON journal_event(repository_id, episode_id, seq)');
|
|
771
786
|
db.exec('CREATE INDEX IF NOT EXISTS idx_event_session ON journal_event(repository_id, session_id, seq)');
|
|
772
787
|
db.exec('CREATE INDEX IF NOT EXISTS idx_event_task ON journal_event(repository_id, task_id)');
|
|
@@ -984,6 +999,8 @@ export interface RecordEventInput {
|
|
|
984
999
|
prompt_id?: string | null;
|
|
985
1000
|
episode_id?: string | null;
|
|
986
1001
|
terminal_state?: TerminalState | null;
|
|
1002
|
+
/** Closed vocabulary; anything else (including absent) records `unknown`. */
|
|
1003
|
+
surface?: Surface | null;
|
|
987
1004
|
return_ref?: string | null;
|
|
988
1005
|
verification_ref?: string | null;
|
|
989
1006
|
consumption?: Consumption | null;
|
|
@@ -1057,6 +1074,10 @@ export function recordEvent(h: JournalHandle, input: RecordEventInput): JournalE
|
|
|
1057
1074
|
worktree_id,
|
|
1058
1075
|
at,
|
|
1059
1076
|
capability: input.capability,
|
|
1077
|
+
// Tolerant by design: a surface this build cannot place is `unknown`,
|
|
1078
|
+
// never a throw. The field answers "which surface" and must not be able
|
|
1079
|
+
// to fail a write that would otherwise have succeeded.
|
|
1080
|
+
surface: readSurface(input.surface),
|
|
1060
1081
|
terminal_state: input.terminal_state ?? null,
|
|
1061
1082
|
return_ref: requireRef('return_ref', input.return_ref),
|
|
1062
1083
|
verification_ref: requireRef('verification_ref', input.verification_ref),
|
|
@@ -1070,9 +1091,9 @@ export function recordEvent(h: JournalHandle, input: RecordEventInput): JournalE
|
|
|
1070
1091
|
`INSERT INTO journal_event
|
|
1071
1092
|
(event, episode_id, session_id, task_id, prompt_id, boundary_status,
|
|
1072
1093
|
boundary_rule_version, repository_id, worktree_id, at, capability,
|
|
1073
|
-
terminal_state, return_ref, verification_ref, consumption,
|
|
1074
|
-
amends_seq)
|
|
1075
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1094
|
+
surface, terminal_state, return_ref, verification_ref, consumption,
|
|
1095
|
+
retain_until, amends_seq)
|
|
1096
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1076
1097
|
)
|
|
1077
1098
|
.run(
|
|
1078
1099
|
row.event,
|
|
@@ -1086,6 +1107,7 @@ export function recordEvent(h: JournalHandle, input: RecordEventInput): JournalE
|
|
|
1086
1107
|
row.worktree_id,
|
|
1087
1108
|
row.at,
|
|
1088
1109
|
row.capability,
|
|
1110
|
+
row.surface,
|
|
1089
1111
|
row.terminal_state,
|
|
1090
1112
|
row.return_ref,
|
|
1091
1113
|
row.verification_ref,
|
|
@@ -1111,6 +1133,7 @@ function toEvent(r: Record<string, unknown>): JournalEvent {
|
|
|
1111
1133
|
worktree_id: String(r['worktree_id']),
|
|
1112
1134
|
at: String(r['at']),
|
|
1113
1135
|
capability: String(r['capability']),
|
|
1136
|
+
surface: readSurface(r['surface']),
|
|
1114
1137
|
// Tolerant, not a cast: a value this build cannot place reads as null.
|
|
1115
1138
|
terminal_state: readRunTerminalState(r['terminal_state']),
|
|
1116
1139
|
return_ref: (r['return_ref'] as string | null) ?? null,
|
|
@@ -28,6 +28,10 @@ import process from 'node:process';
|
|
|
28
28
|
import { parse as parseYaml } from 'yaml';
|
|
29
29
|
|
|
30
30
|
import { inheritedGitOverrides } from './repo_root.js';
|
|
31
|
+
import {
|
|
32
|
+
checkHostPermissionSettings,
|
|
33
|
+
defaultHostPermissionDeps,
|
|
34
|
+
} from './host_permission_checks.js';
|
|
31
35
|
|
|
32
36
|
/** The verdict shape `cmd_doctor` renders. Mirrors its internal `Dict` rows. */
|
|
33
37
|
export interface WiringCheck {
|
|
@@ -291,7 +295,7 @@ export function checkInheritedGitEnv(env: NodeJS.ProcessEnv = process.env): Wiri
|
|
|
291
295
|
* two spreads.
|
|
292
296
|
*/
|
|
293
297
|
/** The four ids, in render order. Spread into `cmd_doctor`'s two id registries. */
|
|
294
|
-
export const WIRING_CHECK_IDS = ['settings-resolution', 'router-artifact', 'hook-resolution', 'inherited-git-env', 'hooks-kill-switch'] as const;
|
|
298
|
+
export const WIRING_CHECK_IDS = ['settings-resolution', 'router-artifact', 'hook-resolution', 'inherited-git-env', 'hooks-kill-switch', 'host-permission-settings'] as const;
|
|
295
299
|
|
|
296
300
|
|
|
297
301
|
/**
|
|
@@ -333,6 +337,7 @@ export function checkHooksDisabled(env: NodeJS.ProcessEnv = process.env): Wiring
|
|
|
333
337
|
|
|
334
338
|
export function wiringRunners(opts: {
|
|
335
339
|
packageRoot: string;
|
|
340
|
+
projectRoot: string;
|
|
336
341
|
iterOverrides: () => Iterable<[string, unknown, string]>;
|
|
337
342
|
}): Record<string, () => WiringCheck> {
|
|
338
343
|
return {
|
|
@@ -344,6 +349,12 @@ export function wiringRunners(opts: {
|
|
|
344
349
|
'hook-resolution': () => checkHookResolution(opts.packageRoot).check,
|
|
345
350
|
'inherited-git-env': () => checkInheritedGitEnv(),
|
|
346
351
|
'hooks-kill-switch': () => checkHooksDisabled(),
|
|
352
|
+
// The host's own permission settings, which decide how often a call is
|
|
353
|
+
// confirmed. It lives with this family rather than its own because
|
|
354
|
+
// `checkHookWiring` below already reads the same `~/.claude/settings.json`
|
|
355
|
+
// for the same host — the question is the same surface, one key over.
|
|
356
|
+
'host-permission-settings': () =>
|
|
357
|
+
checkHostPermissionSettings(defaultHostPermissionDeps(opts.projectRoot)),
|
|
347
358
|
};
|
|
348
359
|
}
|
|
349
360
|
|
|
@@ -175,14 +175,19 @@ export function stop_means_session_end(platform: string | null | undefined): boo
|
|
|
175
175
|
* Deliberately **not** derived from the frequency lattice. That instrument
|
|
176
176
|
* models *slot presence*, and two hosts have a slot that never fires:
|
|
177
177
|
*
|
|
178
|
-
* - `cursor` —
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
178
|
+
* - `cursor` — excluded because reachability there is UNESTABLISHED, not
|
|
179
|
+
* because it is known absent. The earlier wording asserted the CLI fires only
|
|
180
|
+
* shell-execution hooks, sourced to a 2026-01 reading that carried no expiry
|
|
181
|
+
* and could not be re-verified in either direction. The safe direction is the
|
|
182
|
+
* one taken: an unverified slot must not read as covered. The lattice has no
|
|
183
|
+
* IDE/CLI dimension of its own, so it would report cursor covered; the
|
|
184
|
+
* envelope now carries a `surface` field (`_lib/surface.ts`) that records
|
|
185
|
+
* which surface a dispatch came from, and it answers `unknown` on cursor
|
|
186
|
+
* because no marker distinguishes the two.
|
|
182
187
|
* - `cowork` — slots are structurally wired but lifecycle events do not fire
|
|
183
188
|
* (`hook_manifest.yaml:346-351`).
|
|
184
|
-
* - `copilot` —
|
|
185
|
-
* from the join by declaration.
|
|
189
|
+
* - `copilot` — nothing bound by this package (`fallback_only: true` in the
|
|
190
|
+
* manifest), excluded from the join by declaration.
|
|
186
191
|
*
|
|
187
192
|
* A session on an excluded host still *registers* if a `session_start` reaches
|
|
188
193
|
* it, and simply stops being visible after its TTL. It is never reported as
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema helpers for the `node:sqlite` stores in this tree.
|
|
3
|
+
*/
|
|
4
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Add a column to an existing table when it is absent.
|
|
8
|
+
*
|
|
9
|
+
* `CREATE TABLE IF NOT EXISTS` is a no-op on a database that predates a
|
|
10
|
+
* column, so a store created by an earlier build needs the explicit add or the
|
|
11
|
+
* field is silently missing from every read.
|
|
12
|
+
*
|
|
13
|
+
* `PRAGMA table_info` rather than a catch around `ALTER TABLE`: swallowing the
|
|
14
|
+
* error would also swallow a genuine schema failure, and a store that silently
|
|
15
|
+
* fails to migrate reads exactly like one that never needed to.
|
|
16
|
+
*/
|
|
17
|
+
export function addColumnIfMissing(db: DatabaseSync, table: string, column: string, decl: string): void {
|
|
18
|
+
const cols = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];
|
|
19
|
+
if (cols.length === 0) return;
|
|
20
|
+
if (cols.some((c) => c.name === column)) return;
|
|
21
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${decl}`);
|
|
22
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* surface — the IDE / CLI / cloud dimension of a dispatch, as an ENVELOPE
|
|
3
|
+
* field rather than a platform key.
|
|
4
|
+
*
|
|
5
|
+
* WHY A FIELD AND NOT A PLATFORM. `_lib/session_register.ts` records that the
|
|
6
|
+
* capability lattice has no IDE/CLI dimension, which is why it reports cursor
|
|
7
|
+
* covered whether or not its per-turn slots fire there. Splitting `cursor`
|
|
8
|
+
* into `cursor-ide` / `cursor-cli` would change `--platform`, the eight
|
|
9
|
+
* platform identifiers, and every manifest row keyed on them. A field changes
|
|
10
|
+
* none of those and is still the thing `host_lowering.yaml`'s `(host, surface,
|
|
11
|
+
* slot)` key needs.
|
|
12
|
+
*
|
|
13
|
+
* WHAT THIS CAN AND CANNOT DECIDE — the honest half.
|
|
14
|
+
*
|
|
15
|
+
* `unknown` is the default and, today, very nearly the only answer. Nothing in
|
|
16
|
+
* this tree distinguishes an IDE session from a CLI one on any host: no host
|
|
17
|
+
* payload observed here carries such a marker, and inferring it from a TTY or
|
|
18
|
+
* a terminal name would be a guess wearing a function's authority — the exact
|
|
19
|
+
* move the surrounding roadmap exists to remove. So there is no `ide` or `cli`
|
|
20
|
+
* branch. When a host is observed to send a distinguishing marker, it is added
|
|
21
|
+
* to {@link CLOUD_PAYLOAD_MARKERS}'s sibling with a dated citation, and the
|
|
22
|
+
* branch arrives with the evidence rather than ahead of it.
|
|
23
|
+
*
|
|
24
|
+
* The `cloud` branch is a DECLARED VOCABULARY, not an observation. This package
|
|
25
|
+
* will honour these payload keys if a host sends one; none has been observed
|
|
26
|
+
* sending one. That distinction is the point: the mechanism is real and tested,
|
|
27
|
+
* the host claim is absent and says so.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** The closed surface vocabulary. `unknown` is the safe default. */
|
|
31
|
+
export const SURFACES = ['ide', 'cli', 'cloud', 'unknown'] as const;
|
|
32
|
+
export type Surface = (typeof SURFACES)[number];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Payload keys whose truthy presence declares a background / cloud agent.
|
|
36
|
+
*
|
|
37
|
+
* Declared, not observed — see the module header. Kept as a list so a host that
|
|
38
|
+
* starts sending one needs no code change, only a citation in this comment.
|
|
39
|
+
*/
|
|
40
|
+
export const CLOUD_PAYLOAD_MARKERS: readonly string[] = [
|
|
41
|
+
'background_agent',
|
|
42
|
+
'is_background',
|
|
43
|
+
'isBackgroundAgent',
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/** Explicit override, for a caller that already knows the surface. */
|
|
47
|
+
export const SURFACE_ENV_VAR = 'AGENT_CONFIG_SURFACE';
|
|
48
|
+
|
|
49
|
+
function _isSurface(v: unknown): v is Surface {
|
|
50
|
+
return typeof v === 'string' && (SURFACES as readonly string[]).includes(v);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the surface for one dispatch.
|
|
55
|
+
*
|
|
56
|
+
* Precedence: an explicit environment override, then a declared payload
|
|
57
|
+
* marker, then `unknown`. An unrecognised override value is ignored rather
|
|
58
|
+
* than trusted — a typo must not become a capability claim.
|
|
59
|
+
*/
|
|
60
|
+
export function detectSurface(
|
|
61
|
+
payload: unknown,
|
|
62
|
+
env: Record<string, string | undefined> = process.env,
|
|
63
|
+
): Surface {
|
|
64
|
+
const override = env[SURFACE_ENV_VAR];
|
|
65
|
+
if (_isSurface(override)) return override;
|
|
66
|
+
|
|
67
|
+
if (typeof payload === 'object' && payload !== null && !Array.isArray(payload)) {
|
|
68
|
+
const obj = payload as Record<string, unknown>;
|
|
69
|
+
for (const key of CLOUD_PAYLOAD_MARKERS) {
|
|
70
|
+
if (obj[key]) return 'cloud';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return 'unknown';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Read a persisted or caller-supplied surface value.
|
|
78
|
+
*
|
|
79
|
+
* Tolerant, not a cast: an unrecognised value — a row written by a newer build,
|
|
80
|
+
* a typo, an absent field — reads as `unknown`. Same posture as
|
|
81
|
+
* `readRunTerminalState`, and for the same reason: a value this build cannot
|
|
82
|
+
* place must not fail a write or a read.
|
|
83
|
+
*/
|
|
84
|
+
export function readSurface(v: unknown): Surface {
|
|
85
|
+
return _isSurface(v) ? v : 'unknown';
|
|
86
|
+
}
|