@event4u/agent-config 14.5.0 → 14.7.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 +384 -3
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/decision.md +7 -0
- package/dist/agent-src/commands/analyze/inbox.md +264 -10
- package/dist/agent-src/commands/analyze.md +1 -1
- package/dist/agent-src/commands/optimize/project.md +10 -0
- package/dist/agent-src/commands/roadmap/process-full.md +132 -4
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +31 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +161 -1
- package/dist/agent-src/rules/context-hygiene.md +2 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +111 -7
- package/dist/agent-src/rules/fix-what-you-see.md +108 -0
- package/dist/agent-src/rules/no-roadmap-references.md +0 -5
- package/dist/agent-src/rules/recurring-criticism.md +110 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/rule-type-governance.md +0 -2
- package/dist/agent-src/rules/self-repair-loop.md +17 -0
- package/dist/agent-src/rules/skill-quality.md +0 -2
- package/dist/agent-src/rules/source-confidentiality.md +37 -7
- package/dist/agent-src/scripts/archival_sweep.ts +117 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
- package/dist/agent-src/scripts/gate_budget.ts +282 -0
- package/dist/agent-src/scripts/gate_execute.ts +72 -31
- package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
- package/dist/agent-src/scripts/update_roadmap_progress.ts +251 -7
- package/dist/agent-src/skills/adr-create/SKILL.md +25 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
- package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
- package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
- package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
- package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
- package/dist/agent-src/skills/decision-review/SKILL.md +18 -0
- package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
- package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
- package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
- package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
- package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
- package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
- package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
- package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
- package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
- package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +45 -2
- package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/dist/cli/registry.js +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
- package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-4VR4ORQ6.js} +10 -7
- package/dist/cli-delegate/chunk-6EOCNQAM.js +402 -0
- package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-74E3TKWV.js} +381 -280
- package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-7F3VYSM2.js} +4 -4
- package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
- package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
- package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-GJ5BVHXM.js} +72 -165
- package/dist/cli-delegate/{chunk-ZPSXJTLI.js → chunk-JZ4NR2TW.js} +353 -1
- package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
- package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
- package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
- package/dist/cli-delegate/cmd_conformance.js +459 -709
- package/dist/cli-delegate/cmd_doctor.js +6 -6
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +7 -6
- package/dist/cli-delegate/cmd_handoff.js +6 -6
- package/dist/cli-delegate/cmd_packs_active.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +7 -6
- package/dist/cli-delegate/cmd_session_recycle.js +840 -30
- package/dist/cli-delegate/cmd_settings_migrate.js +7 -6
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +7 -6
- package/dist/cli-delegate/cmd_uninstall.js +9 -7
- 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 +7 -6
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +72 -29
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +3127 -2022
- package/dist/install/claudePathsPlan.js +187 -0
- package/dist/install/claudePathsPlan.js.map +1 -0
- package/dist/install/hostLayerFingerprint.js +136 -0
- package/dist/install/hostLayerFingerprint.js.map +1 -0
- package/dist/install/install.mjs +553 -425
- package/dist/install/partitionEligibility.js +276 -0
- package/dist/install/partitionEligibility.js.map +1 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1744 -1191
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_src.js +41 -0
- package/dist/scripts/_lib/agent_src.js.map +1 -1
- package/dist/scripts/_lib/install_layout.js +83 -0
- package/dist/scripts/_lib/install_layout.js.map +1 -0
- package/dist/scripts/_lib/installed_lock.js +444 -0
- package/dist/scripts/_lib/installed_lock.js.map +1 -0
- package/dist/scripts/condense.js +13 -164
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +4 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/contracts/settings-classes.md +12 -6
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
- package/docs/guidelines/agent-infra/engineering-memory-data-format.md +49 -0
- package/docs/guidelines/agent-infra/memory-access.md +17 -0
- package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archival_sweep.ts +117 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
- package/src/agent-src/scripts/gate_budget.ts +282 -0
- package/src/agent-src/scripts/gate_execute.ts +72 -31
- package/src/agent-src/scripts/roadmap_gates.ts +74 -3
- package/src/agent-src/scripts/update_roadmap_progress.ts +251 -7
- package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/src/config/agent-settings.template.yml +27 -2
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/estate-count-budget.json +367 -3
- package/src/config/gate-violation-baselines.json +41 -41
- package/src/config/pack-size-budget.json +3 -2
- package/src/config/rule-activation-census.json +14 -6
- package/src/config/rule-stub-ceilings.json +19 -6
- package/src/install/claudePathsPlan.ts +196 -0
- package/src/install/hostLayerFingerprint.ts +145 -0
- package/src/install/partitionEligibility.ts +325 -0
- package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
- package/src/scripts/_dispatch.bash +6 -1
- package/src/scripts/_lib/ac_heading.ts +30 -0
- package/src/scripts/_lib/agent_src.ts +42 -0
- package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
- package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
- package/src/scripts/_lib/envelope_grounding.ts +38 -8
- package/src/scripts/_lib/installed_lock.ts +50 -5
- package/src/scripts/_lib/layer_overlap_notice.ts +85 -0
- package/src/scripts/_lib/md_section.ts +129 -0
- package/src/scripts/_lib/scope_guard.sh +86 -4
- package/src/scripts/_lib/self_repair.ts +19 -0
- package/src/scripts/_lib/self_repair_class_b.ts +171 -0
- package/src/scripts/_lib/subagent_capsule.ts +81 -1
- package/src/scripts/adr/regenerate_index.ts +13 -2
- package/src/scripts/adr_cite_check.ts +424 -0
- package/src/scripts/ai_council/cli_help.ts +55 -0
- package/src/scripts/before_complete_hook.ts +153 -28
- package/src/scripts/bench_ab_v2_complexity.ts +27 -7
- package/src/scripts/check_adr_frontmatter.ts +187 -1
- package/src/scripts/check_references.ts +60 -0
- package/src/scripts/check_release_published.ts +32 -11
- package/src/scripts/check_single_delivery.ts +517 -0
- package/src/scripts/ci_settle.ts +170 -0
- package/src/scripts/condense.ts +20 -170
- package/src/scripts/council_cli.ts +3 -3
- package/src/scripts/dispatch_r2_reviewer.ts +6 -14
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +77 -14
- package/src/scripts/hooks/block_no_verify.ts +61 -10
- package/src/scripts/hooks/block_unauthorized_git.ts +1 -1
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
- package/src/scripts/hooks/run_continuation_hook.ts +887 -68
- package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
- package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
- package/src/scripts/hooks/state_io.ts +782 -20
- package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
- package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
- package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
- package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
- package/src/scripts/install.sh +12 -2
- package/src/scripts/install.ts +16 -11
- package/src/scripts/language_mirror_hook.ts +561 -25
- package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
- package/src/scripts/lint_plan_risk_register.ts +4 -3
- package/src/scripts/lint_skill_router_head.ts +4 -4
- package/src/scripts/mcp_server/tools.ts +2 -2
- package/src/scripts/memory_consolidation.ts +189 -0
- package/src/scripts/memory_eviction.ts +384 -0
- package/src/scripts/new_roadmap.ts +136 -0
- package/src/scripts/pack_mcp_content.ts +20 -5
- package/src/scripts/release.ts +37 -38
- package/src/scripts/report_layer_overlap.ts +78 -0
- package/src/scripts/report_memory_pointers.ts +622 -0
- package/src/scripts/self_repair_hook.ts +37 -5
- package/src/scripts/session_register_hook.ts +115 -15
- package/src/scripts/skill_usage_collect.ts +11 -1
- package/src/scripts/skill_usage_report.ts +278 -8
- package/src/scripts/sync_pr_branch.ts +7 -0
- package/src/server/schemas/settings.ts +8 -0
- package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
- package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
- /package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
|
@@ -35,8 +35,14 @@ import { check_id_redaction, py_json_dumps_compact_sorted } from './engagement.j
|
|
|
35
35
|
|
|
36
36
|
export const CLASS_A_SCHEMA_VERSION = 1;
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Record classes. `usage` is Phase 1; `self-repair` is Phase 5 step 5.1.
|
|
40
|
+
*
|
|
41
|
+
* Both are Class A — structural fields only — and both travel the same log,
|
|
42
|
+
* spool and transport, discriminated by this field. `skill_usage_report`
|
|
43
|
+
* filters on it so a defect report can never be counted as an invocation.
|
|
44
|
+
*/
|
|
45
|
+
export const ALLOWED_RECORD_CLASSES = ['usage', 'self-repair'] as const;
|
|
40
46
|
|
|
41
47
|
/**
|
|
42
48
|
* Discipline-profile values, the knob that actually decides which rule
|
|
@@ -279,6 +285,122 @@ export function build_class_a_record(input: BuildClassAInput): ClassARecord {
|
|
|
279
285
|
};
|
|
280
286
|
}
|
|
281
287
|
|
|
288
|
+
/**
|
|
289
|
+
* One Class-A SELF-REPAIR record (Phase 5, step 5.1).
|
|
290
|
+
*
|
|
291
|
+
* The shadow of a queued defect: it says a defect of a named class occurred,
|
|
292
|
+
* on which host, at which package version, under which declared profile, for
|
|
293
|
+
* which pseudonymous user. It says nothing about WHAT happened.
|
|
294
|
+
*
|
|
295
|
+
* A SEPARATE TYPE, NOT A WIDENED `ClassARecord`, AND THAT IS THE WHOLE POINT.
|
|
296
|
+
* `DefectFinding` carries `evidence` (a quoted span of the offending text) and
|
|
297
|
+
* `suggested_surface` (a free sentence). Those are the Class-B payload and
|
|
298
|
+
* they ship only on explicit per-case approval. Adding either to a
|
|
299
|
+
* telemetry record type — even optionally, even "sanitized" — would give this
|
|
300
|
+
* type a field capable of holding project content, and the privacy property
|
|
301
|
+
* this whole surface rests on is that no such field exists. There is then no
|
|
302
|
+
* scrubber to fail. Keeping the automatic class in its own interface is how
|
|
303
|
+
* that stays checkable by reading the type instead of auditing every writer.
|
|
304
|
+
*
|
|
305
|
+
* WHAT IS DELIBERATELY ABSENT: an enumerated active-rule / active-skill list.
|
|
306
|
+
* The roadmap Context asks Class A to carry "the active rule and skill
|
|
307
|
+
* snapshot" so that "this rule was loaded in six of seven reports of this
|
|
308
|
+
* class" localizes an artefact. No producer for that exists on a hook path:
|
|
309
|
+
* `match_prompt` needs the compiled router, which nothing in the hook surface
|
|
310
|
+
* loads today and which is not established as present in a consumer install.
|
|
311
|
+
* `discipline_profile` is recorded instead — the knob that decides which rule
|
|
312
|
+
* surfaces load at all — and it is a profile-level snapshot, NOT the
|
|
313
|
+
* per-artefact attribution key. That attribution claim stays unavailable, and
|
|
314
|
+
* saying so here is cheaper than a reader inferring it from a field name.
|
|
315
|
+
*/
|
|
316
|
+
export interface ClassADefectRecord {
|
|
317
|
+
schema_version: number;
|
|
318
|
+
record_class: string;
|
|
319
|
+
ts_bucket: string;
|
|
320
|
+
/** Detector / intake class — a closed vocabulary owned by the caller. */
|
|
321
|
+
defect_class: string;
|
|
322
|
+
/** Which intake produced it — also a closed vocabulary. */
|
|
323
|
+
defect_source: string;
|
|
324
|
+
/** How many times this fingerprint has been seen. A counter, not content. */
|
|
325
|
+
occurrences: number;
|
|
326
|
+
host: string | null;
|
|
327
|
+
package_version: string | null;
|
|
328
|
+
discipline_profile: string | null;
|
|
329
|
+
org_id: string;
|
|
330
|
+
user_hash: string;
|
|
331
|
+
session_hash: string | null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export interface BuildClassADefectInput {
|
|
335
|
+
defect_class: string;
|
|
336
|
+
defect_source: string;
|
|
337
|
+
occurrences: number;
|
|
338
|
+
host: string | null;
|
|
339
|
+
org_id: string;
|
|
340
|
+
salt: string;
|
|
341
|
+
hostname: string;
|
|
342
|
+
username: string;
|
|
343
|
+
session_id: string;
|
|
344
|
+
package_version?: string | null;
|
|
345
|
+
discipline_profile?: string | null;
|
|
346
|
+
now?: Date;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Build one validated Class-A self-repair record. Pure, and throws rather
|
|
351
|
+
* than returning a partial record — same contract as its usage sibling.
|
|
352
|
+
*
|
|
353
|
+
* `defect_class` and `defect_source` go through the same id-redaction floor as
|
|
354
|
+
* every other id here, so a caller that ever passed a path, an extension or
|
|
355
|
+
* an over-long free string gets an exception instead of an egress.
|
|
356
|
+
*/
|
|
357
|
+
export function build_class_a_defect_record(
|
|
358
|
+
input: BuildClassADefectInput,
|
|
359
|
+
): ClassADefectRecord {
|
|
360
|
+
check_id_redaction('defect_class', input.defect_class);
|
|
361
|
+
check_id_redaction('defect_source', input.defect_source);
|
|
362
|
+
check_id_redaction('org_id', input.org_id);
|
|
363
|
+
|
|
364
|
+
const host = input.host ?? null;
|
|
365
|
+
if (host !== null) {
|
|
366
|
+
check_id_redaction('host', host);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const discipline_profile = input.discipline_profile ?? null;
|
|
370
|
+
if (
|
|
371
|
+
discipline_profile !== null
|
|
372
|
+
&& !(ALLOWED_DISCIPLINE_PROFILES as readonly string[]).includes(discipline_profile)
|
|
373
|
+
) {
|
|
374
|
+
throw new ClassARecordError(
|
|
375
|
+
`discipline_profile must be one of ${ALLOWED_DISCIPLINE_PROFILES.join(', ')} or null`,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const package_version = input.package_version ?? null;
|
|
380
|
+
if (package_version !== null) {
|
|
381
|
+
check_id_redaction('package_version', package_version);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!Number.isInteger(input.occurrences) || input.occurrences < 1) {
|
|
385
|
+
throw new ClassARecordError('occurrences must be a positive integer');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
schema_version: CLASS_A_SCHEMA_VERSION,
|
|
390
|
+
record_class: 'self-repair',
|
|
391
|
+
ts_bucket: hour_bucket(input.now ?? new Date()),
|
|
392
|
+
defect_class: input.defect_class,
|
|
393
|
+
defect_source: input.defect_source,
|
|
394
|
+
occurrences: input.occurrences,
|
|
395
|
+
host,
|
|
396
|
+
package_version,
|
|
397
|
+
discipline_profile,
|
|
398
|
+
org_id: input.org_id,
|
|
399
|
+
user_hash: derive_user_hash(input.salt, input.hostname, input.username),
|
|
400
|
+
session_hash: derive_session_hash(input.salt, input.session_id),
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
282
404
|
// ── Retention — the growth budget for the local record log ──────────────
|
|
283
405
|
//
|
|
284
406
|
// Phase 1 shipped this file as append-only with no cap and no pruning, and
|
|
@@ -544,6 +666,20 @@ export function enforce_retention(
|
|
|
544
666
|
return { kept: kept_lines.length, dropped_by_age, dropped_by_size };
|
|
545
667
|
}
|
|
546
668
|
|
|
669
|
+
/** Append one line to one bounded JSONL file. Both targets share it. */
|
|
670
|
+
function _append_and_bound(
|
|
671
|
+
target: string,
|
|
672
|
+
payload: string,
|
|
673
|
+
policy: RetentionPolicy,
|
|
674
|
+
now: Date,
|
|
675
|
+
): void {
|
|
676
|
+
fs.mkdirSync(path.dirname(target) || '.', { recursive: true });
|
|
677
|
+
fs.appendFileSync(target, payload, { encoding: 'utf-8' });
|
|
678
|
+
if (retention_due(target, policy, now)) {
|
|
679
|
+
enforce_retention(target, policy, now);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
547
683
|
/**
|
|
548
684
|
* Append one record as a JSONL line, then enforce the retention policy.
|
|
549
685
|
*
|
|
@@ -555,17 +691,34 @@ export function enforce_retention(
|
|
|
555
691
|
* which makes the common append one extra `stat` plus one bounded read. It
|
|
556
692
|
* lives here rather than in a separate sweep because this is the only writer:
|
|
557
693
|
* a policy a caller has to remember to run is a policy that stops running.
|
|
694
|
+
*
|
|
695
|
+
* THE SPOOL IS WRITTEN HERE, IN THE SAME CALL (Phase 2, step 2.1). When
|
|
696
|
+
* `spool_path` is given, the SAME bytes are appended to the outbound spool
|
|
697
|
+
* under the SAME retention policy, and the session-end flush then only has
|
|
698
|
+
* to drain a file. The alternative — a flush that works out for itself which
|
|
699
|
+
* logged records are still unsent — needs a byte watermark into a file that
|
|
700
|
+
* `enforce_retention` rewrites in place, and a watermark over a compacting
|
|
701
|
+
* file is a silent-corruption pair: the offset survives the prune, the
|
|
702
|
+
* records it points at do not. Enqueue-at-write has no watermark to
|
|
703
|
+
* invalidate and spools each record exactly once, by the only writer.
|
|
704
|
+
*
|
|
705
|
+
* The spool inherits the log's growth budget rather than getting its own,
|
|
706
|
+
* which is what answers Phase 0's unmeasured "queue growth bound across a
|
|
707
|
+
* multi-day outage": a sink that is down for days cannot push the spool past
|
|
708
|
+
* `max_bytes`. The cost of that bound is that the oldest UNSENT records are
|
|
709
|
+
* dropped rather than kept — the same trade the log already makes, stated
|
|
710
|
+
* here rather than discovered later in a truncated file.
|
|
558
711
|
*/
|
|
559
712
|
export function append_class_a_record(
|
|
560
713
|
log_path: string,
|
|
561
|
-
record: ClassARecord,
|
|
714
|
+
record: ClassARecord | ClassADefectRecord,
|
|
562
715
|
policy: RetentionPolicy = DEFAULT_RETENTION_POLICY,
|
|
563
716
|
now: Date = new Date(),
|
|
717
|
+
spool_path: string | null = null,
|
|
564
718
|
): void {
|
|
565
719
|
const payload = `${py_json_dumps_compact_sorted(record as unknown as Record<string, unknown>)}\n`;
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
enforce_retention(log_path, policy, now);
|
|
720
|
+
_append_and_bound(log_path, payload, policy, now);
|
|
721
|
+
if (spool_path !== null && spool_path !== '') {
|
|
722
|
+
_append_and_bound(spool_path, payload, policy, now);
|
|
570
723
|
}
|
|
571
724
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class-A transport — the enqueue half (road-to-org-telemetry Phase 2, step 2.1).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS IS NOT AN INLINE `fetch` AT SESSION END. Phase 0's second spike
|
|
5
|
+
* pre-registered "added latency at or below one second at p95" for an inline
|
|
6
|
+
* flush and measured it FAILING: a healthy sink costs 0.4 ms p95 and a
|
|
7
|
+
* refused connection 0.3 ms, but a BLACKHOLE — a socket that accepts and
|
|
8
|
+
* never answers, the shape a wedged sink actually takes — costs 1002 ms p95
|
|
9
|
+
* against a 1000 ms bar, because the timeout IS the cost. The step's own text
|
|
10
|
+
* defers to that result ("per the second spike's result"), and the result is:
|
|
11
|
+
* session end ENQUEUES ONLY, a detached sender flushes, and local retention
|
|
12
|
+
* carries what did not go out. The same spike measured that fallback at
|
|
13
|
+
* 20.5 ms p95 against the same blackhole — dominated by process spawn — and
|
|
14
|
+
* that is the number this module must not regress.
|
|
15
|
+
*
|
|
16
|
+
* SO THE SPOOL IS WRITTEN BY THE APPENDER, NOT BY THE FLUSH. A session-end
|
|
17
|
+
* flush that had to work out which records were already sent would need a
|
|
18
|
+
* byte watermark into the log, and the log is rewritten in place by
|
|
19
|
+
* `enforce_retention` — a watermark and a compacting file are a
|
|
20
|
+
* silent-corruption pair (offsets survive the prune; the records they point
|
|
21
|
+
* at do not). Enqueue-at-write has no watermark to invalidate: a record is
|
|
22
|
+
* spooled exactly once, by the only writer, in the same call that logs it.
|
|
23
|
+
*
|
|
24
|
+
* TWO PROPERTIES PHASE 0 SAID PHASE 2 MUST MEASURE, AND WHERE THEY LANDED.
|
|
25
|
+
* (1) Does a detached child survive teardown of the session process group —
|
|
26
|
+
* measured, see `agents/evidence/eval-findings/org-telemetry-p2-transport.md`.
|
|
27
|
+
* (2) The queue's growth bound across a multi-day outage — answered by
|
|
28
|
+
* construction here: the spool carries the SAME `RetentionPolicy` as the log,
|
|
29
|
+
* enforced by the same code, so a sink that is down for days cannot grow it
|
|
30
|
+
* past `max_bytes`. That bound is a decision to drop the oldest unsent
|
|
31
|
+
* records rather than to keep all of them, and it is stated rather than left
|
|
32
|
+
* for an operator to find in a truncated file.
|
|
33
|
+
*
|
|
34
|
+
* NO SINK EXISTS YET. `sink-choice` is transferred (a private repository is
|
|
35
|
+
* the council's preferred architecture, but creating it is an org-admin
|
|
36
|
+
* action outside this repository), so nothing in this tree can name a real
|
|
37
|
+
* endpoint. What ships here is the transport an org's own endpoint value
|
|
38
|
+
* activates; with no endpoint the settings never resolve `active` and not a
|
|
39
|
+
* byte moves.
|
|
40
|
+
*/
|
|
41
|
+
import { spawn } from 'node:child_process';
|
|
42
|
+
import * as fs from 'node:fs';
|
|
43
|
+
import * as path from 'node:path';
|
|
44
|
+
|
|
45
|
+
import {
|
|
46
|
+
DEFAULT_RETENTION_POLICY,
|
|
47
|
+
enforce_retention,
|
|
48
|
+
retention_due,
|
|
49
|
+
type RetentionPolicy,
|
|
50
|
+
} from './remote.js';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The outbound-flush timeout, in ms.
|
|
54
|
+
*
|
|
55
|
+
* The step says "at or below one second"; this is the bar itself rather than
|
|
56
|
+
* a value under it, because the spike measured the timeout as the dominant
|
|
57
|
+
* cost only for an INLINE flush. In the detached child the wait costs the
|
|
58
|
+
* session nothing, so shortening it would only turn recoverable slow sinks
|
|
59
|
+
* into dropped batches.
|
|
60
|
+
*/
|
|
61
|
+
export const DEFAULT_FLUSH_TIMEOUT_MS = 1000;
|
|
62
|
+
|
|
63
|
+
/** `never` means no transport at all — no spool is written. */
|
|
64
|
+
export const FLUSH_NEVER = 'never';
|
|
65
|
+
|
|
66
|
+
/** The one flush policy that spools and sends. */
|
|
67
|
+
export const FLUSH_SESSION_END = 'session-end';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The spool that sits beside a record log.
|
|
71
|
+
*
|
|
72
|
+
* Derived rather than configured: a second path key would let an install
|
|
73
|
+
* point the spool somewhere the appender does not write, and the failure —
|
|
74
|
+
* records logged locally, nothing ever sent — is invisible. `.jsonl` is
|
|
75
|
+
* replaced rather than appended to so the spool is not itself picked up by a
|
|
76
|
+
* reader globbing `*.jsonl` for records.
|
|
77
|
+
*/
|
|
78
|
+
export function spool_path_for(log_path: string): string {
|
|
79
|
+
const dir = path.dirname(log_path);
|
|
80
|
+
const base = path.basename(log_path).replace(/\.jsonl$/u, '');
|
|
81
|
+
return path.join(dir, `${base}.spool.jsonl`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Append one already-serialised record line to the spool and bound it.
|
|
86
|
+
*
|
|
87
|
+
* The line is passed in rather than re-serialised so the spooled bytes are
|
|
88
|
+
* byte-identical to the logged bytes: a sink and a local inspection of the
|
|
89
|
+
* same record must not be able to disagree.
|
|
90
|
+
*/
|
|
91
|
+
export function enqueue_line(
|
|
92
|
+
spool_path: string,
|
|
93
|
+
line: string,
|
|
94
|
+
policy: RetentionPolicy = DEFAULT_RETENTION_POLICY,
|
|
95
|
+
now: Date = new Date(),
|
|
96
|
+
): void {
|
|
97
|
+
fs.mkdirSync(path.dirname(spool_path) || '.', { recursive: true });
|
|
98
|
+
fs.appendFileSync(spool_path, line, { encoding: 'utf-8' });
|
|
99
|
+
if (retention_due(spool_path, policy, now)) {
|
|
100
|
+
enforce_retention(spool_path, policy, now);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface SpawnSenderInput {
|
|
105
|
+
/** Absolute path to `flush_sender.mjs`. */
|
|
106
|
+
readonly sender_script: string;
|
|
107
|
+
readonly spool_path: string;
|
|
108
|
+
readonly endpoint: string;
|
|
109
|
+
readonly timeout_ms?: number;
|
|
110
|
+
/** Node binary. Defaults to the one running this process. */
|
|
111
|
+
readonly node_path?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Spawn the detached sender and return immediately.
|
|
116
|
+
*
|
|
117
|
+
* `detached: true` + `stdio: 'ignore'` + `unref()` is the exact shape the
|
|
118
|
+
* spike measured at 20.5 ms p95, and all three are load-bearing: without
|
|
119
|
+
* `detached` the child dies with the session's process group, without
|
|
120
|
+
* `ignore` the parent holds pipe fds open, and without `unref` the parent's
|
|
121
|
+
* event loop waits for the child — which would reintroduce the very latency
|
|
122
|
+
* the spool exists to remove.
|
|
123
|
+
*
|
|
124
|
+
* The sender is a PLAIN-NODE `.mjs` on purpose. Its siblings in this
|
|
125
|
+
* directory are TypeScript resolved by `tsx` at import time; a detached child
|
|
126
|
+
* has no such resolution to inherit, so a `.ts` sender would depend on a
|
|
127
|
+
* `tsx` binary being findable in a consumer install at teardown — the least
|
|
128
|
+
* observable moment there is. `process.execPath` is the node already running
|
|
129
|
+
* this hook, so it exists by construction.
|
|
130
|
+
*
|
|
131
|
+
* Returns `true` when the child was spawned. Never throws: a transport that
|
|
132
|
+
* can fail a session is worse than a transport that loses a batch, and the
|
|
133
|
+
* batch is not lost — it stays in the spool for the next flush.
|
|
134
|
+
*/
|
|
135
|
+
export function spawn_detached_sender(input: SpawnSenderInput): boolean {
|
|
136
|
+
try {
|
|
137
|
+
const child = spawn(
|
|
138
|
+
input.node_path ?? process.execPath,
|
|
139
|
+
[
|
|
140
|
+
input.sender_script,
|
|
141
|
+
'--spool',
|
|
142
|
+
input.spool_path,
|
|
143
|
+
'--endpoint',
|
|
144
|
+
input.endpoint,
|
|
145
|
+
'--timeout',
|
|
146
|
+
String(input.timeout_ms ?? DEFAULT_FLUSH_TIMEOUT_MS),
|
|
147
|
+
],
|
|
148
|
+
{ detached: true, stdio: 'ignore' },
|
|
149
|
+
);
|
|
150
|
+
child.unref();
|
|
151
|
+
return true;
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Is there anything to send?
|
|
159
|
+
*
|
|
160
|
+
* Checked before the spawn so an idle session pays a `stat` rather than a
|
|
161
|
+
* process. Most sessions invoke no skill at all.
|
|
162
|
+
*/
|
|
163
|
+
export function spool_has_work(spool_path: string): boolean {
|
|
164
|
+
try {
|
|
165
|
+
return fs.statSync(spool_path).size > 0;
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -395,8 +395,9 @@ project:
|
|
|
395
395
|
# public -> a product with an intended market. Full three-question gate
|
|
396
396
|
# and the full ladder.
|
|
397
397
|
# Default `public` = the behaviour every install had before this key existed.
|
|
398
|
-
# Absent resolves to `public`
|
|
399
|
-
#
|
|
398
|
+
# Absent resolves to `public` too, mechanically: the template-defaults layer
|
|
399
|
+
# supplies this value beneath every file layer (agent_settings.ts:873) and
|
|
400
|
+
# this is not a settingsCarveOut key, so absent == an explicit `public`.
|
|
400
401
|
audience: public
|
|
401
402
|
|
|
402
403
|
# --- GitHub integration ---
|
|
@@ -587,6 +588,30 @@ roadmap:
|
|
|
587
588
|
# only if your team genuinely commits work on a calendar plate.
|
|
588
589
|
horizon_weeks: 0
|
|
589
590
|
|
|
591
|
+
# --- Class-1 gate budget (road-to-gate-autonomy Phase 2) ---
|
|
592
|
+
#
|
|
593
|
+
# The standing spend bound for CLASS-1 roadmap blockers — the gate class
|
|
594
|
+
# that is billable but reversible, where the human ingredient is spend
|
|
595
|
+
# consent rather than judgment (`agent-config gates --execute <id>`).
|
|
596
|
+
# Decided at `b-gate-budget-preauth`, option (a): TWO caps, because a
|
|
597
|
+
# per-run cap alone bounds one mistake and not a week of them. Every
|
|
598
|
+
# authorised run appends a receipt to
|
|
599
|
+
# `agents/runtime/state/gate-budget-ledger.jsonl`, which is the audit
|
|
600
|
+
# surface the rolling cap reads back.
|
|
601
|
+
#
|
|
602
|
+
# These caps bound the SIZE of an authorised spend. They never supply the
|
|
603
|
+
# authorisation: `gates --execute` still requires `--confirm` on every
|
|
604
|
+
# class-1 run, and a class-1 entry over either cap renders its consent
|
|
605
|
+
# line instead of running.
|
|
606
|
+
gate_budget:
|
|
607
|
+
# Per-run ceiling in USD. A class-1 entry whose `Budget:` field states a
|
|
608
|
+
# larger figure renders instead of running.
|
|
609
|
+
max_cost_per_run_usd: 5
|
|
610
|
+
|
|
611
|
+
# Rolling-window ceiling in USD over the last 7 days, summed from the
|
|
612
|
+
# receipt ledger. A run whose estimate would cross it renders instead.
|
|
613
|
+
max_cost_per_rolling_7d_usd: 25
|
|
614
|
+
|
|
590
615
|
# --- Plan governance gates ---
|
|
591
616
|
#
|
|
592
617
|
# Three governance gates around the plan/delivery workflow (contract:
|
|
@@ -184,6 +184,24 @@ ci_only:
|
|
|
184
184
|
# worth remote enforcement, OR it should simply be added to a workflow. Prefer
|
|
185
185
|
# adding it: a local-only gate is the direction that let real defects merge.
|
|
186
186
|
local_only:
|
|
187
|
+
- id: check_single_delivery
|
|
188
|
+
class: developer-machine-scoped
|
|
189
|
+
reason: >
|
|
190
|
+
Compares the two host artefact layers — `~/.claude/**` against
|
|
191
|
+
`<project>/.claude/**` — and the overlap it looks for is a property of the
|
|
192
|
+
DEVELOPER MACHINE, not of the branch. In CI there is nothing to compare:
|
|
193
|
+
`.claude/` is gitignored and no workflow leg installs at user scope, so a
|
|
194
|
+
run finds zero layers on both sides and can only ever report "nothing was
|
|
195
|
+
compared". Measured, not assumed — that is why the gate carries an
|
|
196
|
+
OPTIONAL_SURFACE `allowEmpty` reason rather than treating the absence as a
|
|
197
|
+
dead scope. Declared rather than wired for the same reason
|
|
198
|
+
`check_pr_ci_current` below is: there is no remote equivalent to write, and
|
|
199
|
+
writing one would measure a topology no contributor has. The AI council
|
|
200
|
+
(2/2, 2026-08-19) converged on preflight for exactly this argument. Note the
|
|
201
|
+
gate is registered in REPORT mode while `road-to-single-delivery` Phase 2 is
|
|
202
|
+
halted; the `--enforce` flip lands with the partition and does not change
|
|
203
|
+
this declaration, because enforcing remotely would still have nothing to
|
|
204
|
+
enforce against.
|
|
187
205
|
- id: check_pr_ci_current
|
|
188
206
|
class: pre-push-scoped
|
|
189
207
|
reason: >
|