@event4u/agent-config 14.18.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 +169 -130
- package/README.md +71 -149
- package/dist/agent-src/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +2 -2
- 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/adversarial-review/SKILL.md +0 -8
- package/dist/agent-src/skills/canvas-design/SKILL.md +1 -1
- 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 +1 -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/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/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 +17 -2
- 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 +420 -16
- 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 +28 -27
- 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 +2132 -1462
- package/dist/install/install.mjs +588 -527
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1619 -1045
- package/dist/scripts/_lib/model_tier.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/gate-coverage.yml +170 -0
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/release-gate-locality.yml +12 -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 +93 -0
- package/src/scripts/_lib/release_material.ts +64 -1
- 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/ui_authority.ts +116 -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 +66 -9
- package/src/scripts/check_gate_coverage.ts +96 -0
- package/src/scripts/check_release_highlights.ts +74 -1
- package/src/scripts/check_skill_activation_claim.ts +179 -0
- package/src/scripts/check_trigger_evals.ts +1 -1
- package/src/scripts/conformance_scan.ts +44 -3
- package/src/scripts/explain_run.ts +1 -1
- package/src/scripts/generate_capability_matrix.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +75 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +14 -11
- package/src/scripts/hooks/augment-dispatcher.sh +1 -1
- package/src/scripts/hooks/category_a.ts +291 -0
- package/src/scripts/hooks/cline-dispatcher.sh +1 -1
- package/src/scripts/hooks/cursor-dispatcher.sh +11 -5
- package/src/scripts/hooks/dispatch_hook.ts +17 -22
- package/src/scripts/hooks/git_command_classifier.ts +156 -1
- 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_status.ts +12 -10
- package/src/scripts/install.ts +53 -82
- package/src/scripts/install_anthropic_key.sh +2 -2
- 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/report_skill_activation.ts +319 -7
- package/src/scripts/schemas/review-findings.schema.json +5 -0
- package/src/scripts/schemas/skill.schema.json +2 -2
- 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
|
@@ -351,6 +351,69 @@ export const MIX_RESPONSE_PLACEHOLDERS: readonly string[] = [
|
|
|
351
351
|
export function render_mix_response(level: string): string[] {
|
|
352
352
|
return [
|
|
353
353
|
`> ${MIX_RESPONSE_MARKER} ${level}.`,
|
|
354
|
-
`>
|
|
354
|
+
`> ${PROMISE_PHRASE} ${MIX_RESPONSE_PLACEHOLDER}, tracked in <roadmap or issue>.`,
|
|
355
355
|
];
|
|
356
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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -341,3 +341,119 @@ export function operationConflicts(authority: UiAuthority, op: Operation): Confl
|
|
|
341
341
|
}
|
|
342
342
|
return out;
|
|
343
343
|
}
|
|
344
|
+
|
|
345
|
+
/* -------------------------------------------------------------------------- */
|
|
346
|
+
/* Antipattern signal classes against the precedence list */
|
|
347
|
+
/* -------------------------------------------------------------------------- */
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* The five classes a `design-antipatterns.md` catalog row may claim.
|
|
351
|
+
*
|
|
352
|
+
* Kept here, beside the precedence list, rather than in the catalog gate: the
|
|
353
|
+
* question "may this signal overrule what the user supplied" is a precedence
|
|
354
|
+
* question, and precedence lives in exactly one module by construction.
|
|
355
|
+
*/
|
|
356
|
+
export type SignalClass =
|
|
357
|
+
| 'floor'
|
|
358
|
+
| 'invariant'
|
|
359
|
+
| 'craft-presumption'
|
|
360
|
+
| 'style-preference'
|
|
361
|
+
| 'reference-constraint';
|
|
362
|
+
|
|
363
|
+
/** The two classes that are opinions rather than thresholds. */
|
|
364
|
+
export const SOFT_CLASSES = ['craft-presumption', 'style-preference'] as const;
|
|
365
|
+
|
|
366
|
+
export interface SignalVerdict {
|
|
367
|
+
/**
|
|
368
|
+
* `blocks` — refuse the surface. `flags` — surface it as a rebuttable
|
|
369
|
+
* presumption. `yields` — say nothing: something with more authority has
|
|
370
|
+
* already decided this.
|
|
371
|
+
*/
|
|
372
|
+
outcome: 'blocks' | 'flags' | 'yields';
|
|
373
|
+
/** The precedence level that outranked it, numbered as in this file's header. */
|
|
374
|
+
outranked_by: { level: number; source: ProvenanceSource; detail: string } | null;
|
|
375
|
+
reason: string;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Where a soft signal sits against the precedence list.
|
|
380
|
+
*
|
|
381
|
+
* A craft presumption is a good default and nothing more. When the run has a
|
|
382
|
+
* supplied reference (4), a coherent incumbent (5), or a project `DESIGN.md`
|
|
383
|
+
* register (6), that artifact has already decided the question the presumption
|
|
384
|
+
* would re-open, and re-opening it is the redesign-by-taste failure
|
|
385
|
+
* `design-fidelity` exists to stop.
|
|
386
|
+
*
|
|
387
|
+
* A `floor` or an `invariant` is NOT a taste call — an accessibility threshold
|
|
388
|
+
* or a structural correctness rule — so nothing on the list releases it, and a
|
|
389
|
+
* reference artifact that violates one is a finding about the reference.
|
|
390
|
+
*
|
|
391
|
+
* LEVELS 1–3 ARE DELIBERATELY NOT MECHANISED HERE. Explicit user authority, a
|
|
392
|
+
* registered hard constraint, and the surface brief all outrank a presumption a
|
|
393
|
+
* fortiori, but `Signals` carries them only as whole-surface fields
|
|
394
|
+
* (`surface_mode`, `register`, `change_intent`) with no per-pattern statement.
|
|
395
|
+
* Reading a brief's mere presence as blanket permission for every antipattern
|
|
396
|
+
* would silence the catalog on every briefed surface, which is the opposite of
|
|
397
|
+
* what those levels mean. A per-pattern override is `DESIGN.md`'s declared
|
|
398
|
+
* suppression, which is level 6 and IS handled.
|
|
399
|
+
*/
|
|
400
|
+
export function resolveSignal(signalClass: SignalClass, signals: Signals = {}): SignalVerdict {
|
|
401
|
+
if (signalClass === 'floor' || signalClass === 'invariant') {
|
|
402
|
+
return {
|
|
403
|
+
outcome: 'blocks',
|
|
404
|
+
outranked_by: null,
|
|
405
|
+
reason:
|
|
406
|
+
`a ${signalClass} is a threshold, not a preference — no supplied artifact releases it, `
|
|
407
|
+
+ 'and an artifact that violates one is a finding about the artifact',
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (signalClass === 'reference-constraint') {
|
|
412
|
+
const present = signals.reference !== undefined || signals.design_md?.present === true;
|
|
413
|
+
return present
|
|
414
|
+
? {
|
|
415
|
+
outcome: 'blocks',
|
|
416
|
+
outranked_by: null,
|
|
417
|
+
reason: 'the reference or DESIGN.md this constraint is bound to is present in this run, so it carries that artifact\'s own authority',
|
|
418
|
+
}
|
|
419
|
+
: {
|
|
420
|
+
outcome: 'flags',
|
|
421
|
+
outranked_by: null,
|
|
422
|
+
reason: 'the reference this constraint is bound to is not present in this run, so it has no artifact behind it',
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (signals.reference !== undefined) {
|
|
427
|
+
return {
|
|
428
|
+
outcome: 'yields',
|
|
429
|
+
outranked_by: { level: 4, source: 'reference-artifact', detail: signals.reference.path ?? signals.reference.maturity },
|
|
430
|
+
reason: `a ${signalClass} never outranks a supplied reference artifact`,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (signals.incumbent?.coherent === true) {
|
|
434
|
+
return {
|
|
435
|
+
outcome: 'yields',
|
|
436
|
+
outranked_by: { level: 5, source: 'incumbent-scan', detail: 'coherent incumbent' },
|
|
437
|
+
reason: `a ${signalClass} never outranks a coherent incumbent`,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (signals.design_md?.present === true) {
|
|
441
|
+
return {
|
|
442
|
+
outcome: 'yields',
|
|
443
|
+
outranked_by: { level: 6, source: 'design-md', detail: signals.design_md.register ?? 'register unstated' },
|
|
444
|
+
reason: `a ${signalClass} never outranks a project DESIGN.md register`,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (signals.product_md?.present === true) {
|
|
448
|
+
return {
|
|
449
|
+
outcome: 'yields',
|
|
450
|
+
outranked_by: { level: 6, source: 'product-md', detail: signals.product_md.register ?? 'register unstated' },
|
|
451
|
+
reason: `a ${signalClass} never outranks a project PRODUCT.md register`,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
outcome: 'flags',
|
|
456
|
+
outranked_by: null,
|
|
457
|
+
reason: `a ${signalClass} with nothing above it on the precedence list is a rebuttable presumption`,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
@@ -16,7 +16,7 @@ import * as fs from 'node:fs';
|
|
|
16
16
|
import * as path from 'node:path';
|
|
17
17
|
import process from 'node:process';
|
|
18
18
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
19
|
-
import { readAdrAxisCells, readAdrFrontmatterScalars } from '../_lib/adr_frontmatter.js';
|
|
19
|
+
import { dated_trigger_day, readAdrAxisCells, readAdrFrontmatterScalars } from '../_lib/adr_frontmatter.js';
|
|
20
20
|
import { generatedByItalic } from '../_lib/generated_by.js';
|
|
21
21
|
|
|
22
22
|
// ^ADR-(\d{3})-([a-z0-9-]+)\.md$
|
|
@@ -262,6 +262,41 @@ export function scanOutside(repoRoot: string, roots: readonly string[] = OUTSIDE
|
|
|
262
262
|
return out;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
/**
|
|
266
|
+
* The rows whose `review_trigger` opens with a calendar date.
|
|
267
|
+
*
|
|
268
|
+
* WHY THIS SECTION CARRIES THE DATE AND NOT THE FIRED/NOT-FIRED STATE
|
|
269
|
+
* ------------------------------------------------------------------
|
|
270
|
+
* `regenerate_index --check` byte-compares this file in CI
|
|
271
|
+
* (`.github/workflows/rule-backstops.yml`), so anything rendered here has to be
|
|
272
|
+
* a function of the tree alone. A `fired` / `not-fired` column would be a
|
|
273
|
+
* function of the tree AND the day, which means the index would go stale on a
|
|
274
|
+
* calendar boundary nobody committed and red every open pull request until
|
|
275
|
+
* someone regenerated it. The date is the decidable half; the reader compares
|
|
276
|
+
* it to today, and `adr_cite_check` prints the comparison for a machine.
|
|
277
|
+
*/
|
|
278
|
+
export interface DatedTriggerRow {
|
|
279
|
+
label: string;
|
|
280
|
+
path: string;
|
|
281
|
+
day: string;
|
|
282
|
+
decision: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function datedTriggerRows(rows: readonly Row[]): DatedTriggerRow[] {
|
|
286
|
+
const out: DatedTriggerRow[] = [];
|
|
287
|
+
for (const r of rows) {
|
|
288
|
+
const day = dated_trigger_day(r.review_trigger ?? '');
|
|
289
|
+
if (day === null) continue;
|
|
290
|
+
out.push({
|
|
291
|
+
label: 'num' in r ? `ADR-${r.num as string}` : (r.path as string).slice(0, -3),
|
|
292
|
+
path: r.path as string,
|
|
293
|
+
day,
|
|
294
|
+
decision: r.decision ?? r.slug ?? '—',
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return out.sort((a, b) => a.day.localeCompare(b.day) || a.label.localeCompare(b.label));
|
|
298
|
+
}
|
|
299
|
+
|
|
265
300
|
/** Mirror `render(num, leg)`. */
|
|
266
301
|
export function render(num: Row[], leg: Row[], outside: OutsideRow[] = []): string {
|
|
267
302
|
const out = [
|
|
@@ -277,6 +312,24 @@ export function render(num: Row[], leg: Row[], outside: OutsideRow[] = []): stri
|
|
|
277
312
|
if (leg.length > 0) {
|
|
278
313
|
out.push('', '## Unnumbered (legacy)', '', HEAD, ...leg.map(row));
|
|
279
314
|
}
|
|
315
|
+
const dated = datedTriggerRows([...num, ...leg]);
|
|
316
|
+
if (dated.length > 0) {
|
|
317
|
+
out.push(
|
|
318
|
+
'',
|
|
319
|
+
'## Dated review triggers',
|
|
320
|
+
'',
|
|
321
|
+
'A `review_trigger` that opens with a calendar date is the one reopen condition',
|
|
322
|
+
'this repository can decide without interpreting anything, so it is listed where a',
|
|
323
|
+
'reader meets it rather than only in a command they would have to know to run.',
|
|
324
|
+
'Compare the expiry to today; `./scripts-run src/scripts/adr_cite_check <ADR-NNN>`',
|
|
325
|
+
'prints `fired` or `not-fired` for the same date. Every other trigger is semantic',
|
|
326
|
+
'and reports `indeterminate` — a trigger that merely mentions a date is not listed.',
|
|
327
|
+
'',
|
|
328
|
+
'| Expiry | # | Decision |',
|
|
329
|
+
'|---|---|---|',
|
|
330
|
+
...dated.map((d) => `| ${d.day} | [${d.label}](${d.path}) | ${d.decision} |`),
|
|
331
|
+
);
|
|
332
|
+
}
|
|
280
333
|
if (outside.length > 0) {
|
|
281
334
|
out.push(
|
|
282
335
|
'',
|