@event4u/agent-config 14.1.0 → 14.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +291 -181
- package/agents/templates/.ai-council.yml.example +35 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +83 -12
- package/dist/agent-src/rules/roadmap-progress-sync.md +48 -4
- package/dist/agent-src/scripts/resume_probe.ts +154 -9
- package/dist/agent-src/scripts/roadmap_gates.ts +210 -6
- package/dist/agent-src/skills/roadmap-management/SKILL.md +9 -4
- package/dist/agent-src/templates/agent-settings.md +70 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/telemetry/remote.ts +571 -0
- package/dist/agent-src/templates/scripts/telemetry/settings.ts +222 -44
- package/dist/cli/python/workspace_drive.js +8 -3
- package/dist/cli/python/workspace_drive.js.map +1 -1
- package/dist/cli/python/workspace_hosts.js +16 -12
- package/dist/cli/python/workspace_hosts.js.map +1 -1
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-F3YUF7SA.js → chunk-3UCJWMMP.js} +1 -1
- package/dist/cli-delegate/chunk-F7DQWYR5.js +69 -0
- package/dist/cli-delegate/{chunk-7PZ4FSZB.js → chunk-FBKL6JWT.js} +61 -2
- package/dist/cli-delegate/{chunk-MNWRDMR5.js → chunk-PKOBQADP.js} +1 -1
- package/dist/cli-delegate/cmd_conformance.js +26 -11
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +3 -3
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +17 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +4 -4
- 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 +3773 -1731
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1070 -408
- package/dist/scripts/_lib/host_launch.js +66 -0
- package/dist/scripts/_lib/host_launch.js.map +1 -0
- package/dist/server/io/yamlIO.js +175 -3
- package/dist/server/io/yamlIO.js.map +1 -1
- package/dist/server/routes/wizard.js +20 -1
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/shared/settingsConsent.js +25 -0
- package/dist/shared/settingsConsent.js.map +1 -1
- package/docs/contracts/settings-classes.md +11 -0
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +2 -1
- package/package.json +1 -1
- package/src/agent-src/scripts/resume_probe.ts +154 -9
- package/src/agent-src/scripts/roadmap_gates.ts +210 -6
- package/src/agent-src/templates/scripts/telemetry/remote.ts +571 -0
- package/src/agent-src/templates/scripts/telemetry/settings.ts +222 -44
- package/src/config/estate-count-budget.json +120 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +32 -4
- package/src/config/gate-violation-baselines.json +63 -63
- package/src/config/gitignore-block.txt +5 -0
- package/src/config/hook-latency-budget.json +40 -1
- package/src/config/hook-token-budget.json +22 -0
- package/src/config/rule-stub-ceilings.json +11 -3
- package/src/scripts/_dispatch.bash +42 -1
- package/src/scripts/_lib/council_fallback_posture.ts +81 -0
- package/src/scripts/_lib/council_fallback_wiring.ts +126 -0
- package/src/scripts/_lib/council_settings_block.ts +152 -0
- package/src/scripts/_lib/headless_invocation.ts +285 -0
- package/src/scripts/_lib/host_launch.ts +72 -0
- package/src/scripts/_lib/map_to_object.ts +16 -0
- package/src/scripts/_lib/roadmap_checkboxes.ts +88 -0
- package/src/scripts/_lib/run_checkpoint.ts +409 -0
- package/src/scripts/_lib/session_register.ts +32 -1
- package/src/scripts/_lib/skill_catalogue_series.ts +91 -0
- package/src/scripts/_lib/stdin.ts +35 -1
- package/src/scripts/_lib/turn_end_refusals.ts +12 -4
- package/src/scripts/_lib/unattended_guard.ts +371 -0
- package/src/scripts/ai_council/config.ts +27 -1
- package/src/scripts/ai_council/events_log.ts +12 -1
- package/src/scripts/ai_council/fallback_config.ts +114 -0
- package/src/scripts/ai_council/mid_flight_fallback.ts +295 -0
- package/src/scripts/ai_council/orchestrator.ts +251 -292
- package/src/scripts/ai_council/py_parity.ts +139 -0
- package/src/scripts/ai_council/response_render.ts +99 -0
- package/src/scripts/ai_council/spend_gate.ts +123 -0
- package/src/scripts/ai_council/transport_resolver.ts +81 -13
- package/src/scripts/bench_hook_injection.ts +100 -6
- package/src/scripts/bench_hook_latency.ts +442 -16
- package/src/scripts/check_estate_count.ts +924 -0
- package/src/scripts/compile_hook_manifest.ts +67 -0
- package/src/scripts/council_cli.ts +100 -125
- package/src/scripts/decision_memo.ts +325 -0
- package/src/scripts/dispatch_r2_reviewer.ts +169 -15
- package/src/scripts/hook_manifest.json +1 -0
- package/src/scripts/hook_manifest.yaml +172 -25
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/dispatch_hook.ts +183 -10
- package/src/scripts/hooks/dispatch_issues.ts +26 -0
- package/src/scripts/hooks/end_review_nudge_hook.ts +97 -24
- package/src/scripts/hooks/hook_stdin.ts +111 -5
- package/src/scripts/hooks/injection_budget.ts +595 -0
- package/src/scripts/hooks/payload_stub.ts +446 -0
- package/src/scripts/hooks/run_continuation_hook.ts +627 -0
- package/src/scripts/hooks/session_eol_hook.ts +52 -0
- package/src/scripts/hooks/skill_route_hook.ts +172 -11
- package/src/scripts/hooks/telemetry_usage_hook.ts +339 -0
- package/src/scripts/hooks/tool_result_bytes_hook.ts +27 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +45 -7
- package/src/scripts/hooks_doctor.ts +16 -2
- package/src/scripts/install-hooks.sh +26 -0
- package/src/scripts/interruption_report.ts +330 -13
- package/src/scripts/lint_hook_manifest.ts +167 -0
- package/src/scripts/roadmap_progress_hook.ts +254 -46
- package/src/scripts/run_supervise.ts +623 -0
- package/src/scripts/session_register_hook.ts +108 -8
- package/src/scripts/sessions_cli.ts +18 -6
- package/src/scripts/telemetry_disclosure_hook.ts +244 -0
- package/src/server/io/yamlIO.ts +167 -3
- package/src/shared/settingsConsent.ts +49 -2
- package/dist/cli-delegate/chunk-MMJL4TIX.js +0 -18
|
@@ -342,6 +342,201 @@ function render(
|
|
|
342
342
|
* Returns '' when nothing is owned by the user, so the caller can append the
|
|
343
343
|
* result unconditionally and get silence when silence is correct.
|
|
344
344
|
*/
|
|
345
|
+
/**
|
|
346
|
+
* Whether a recorded recommendation was drafted by the agent rather than decided
|
|
347
|
+
* by the maintainer.
|
|
348
|
+
*
|
|
349
|
+
* Read from the field's own text, because that is where the claim has to live: a
|
|
350
|
+
* drafted recommendation is written INTO the roadmap so every consumer
|
|
351
|
+
* (`gates`, `--reply`, the sheet) sees the same string, and a marker held only
|
|
352
|
+
* in the sheet would be invisible to the other two. The convention is a leading
|
|
353
|
+
* `(agent-drafted …)` — matched loosely on purpose, since the parenthetical also
|
|
354
|
+
* carries a date and a sentence of provenance.
|
|
355
|
+
*/
|
|
356
|
+
export function isAgentDrafted(recommendation: string): boolean {
|
|
357
|
+
return /\(agent-drafted\b/i.test(recommendation);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* How a row names its decision.
|
|
362
|
+
*
|
|
363
|
+
* `legacy` is the parser's PLACEHOLDER id for a `> Blocked until …` note, not an
|
|
364
|
+
* identifier anyone wrote — `renderEntry` already substitutes "blocked-until
|
|
365
|
+
* note" for exactly that reason, and the sheet printing the raw placeholder made
|
|
366
|
+
* the two views disagree about the same row.
|
|
367
|
+
*/
|
|
368
|
+
export function sheetLabel(b: Blocker): string {
|
|
369
|
+
return isLegacy(b) ? 'blocked-until note' : `\`${b.id}\``;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** First sentence of a passage, for the one-line `Default:` column. */
|
|
373
|
+
function firstSentence(text: string): string {
|
|
374
|
+
const trimmed = text.trim();
|
|
375
|
+
if (trimmed === '') {
|
|
376
|
+
return '';
|
|
377
|
+
}
|
|
378
|
+
const m = /^(.+?[.!?])(\s|$)/.exec(trimmed.replace(/\s+/g, ' '));
|
|
379
|
+
return m === null ? trimmed.replace(/\s+/g, ' ') : (m[1] as string);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* The one-line question a sheet row leads with.
|
|
384
|
+
*
|
|
385
|
+
* `Question:` is an optional field and only 5 of the 21 user-owned entries carry
|
|
386
|
+
* one (measured 2026-08-18), so a sheet that printed it and nothing else would
|
|
387
|
+
* be blank for three quarters of the estate. Falling back to the first step of
|
|
388
|
+
* `What to do:` and then to `Blocks:` is deterministic and derived from the
|
|
389
|
+
* blocker's own text — which is why the row LABELS which of the three it used
|
|
390
|
+
* rather than presenting all three as the same kind of statement.
|
|
391
|
+
*/
|
|
392
|
+
export function sheetQuestion(b: Blocker): { text: string; source: 'question' | 'todo' | 'blocks' } {
|
|
393
|
+
if (b.question.trim() !== '') {
|
|
394
|
+
return { text: firstSentence(b.question), source: 'question' };
|
|
395
|
+
}
|
|
396
|
+
const steps = regroupTodo(b.todo);
|
|
397
|
+
if (steps.length > 0 && (steps[0] as string).trim() !== '') {
|
|
398
|
+
return { text: firstSentence(steps[0] as string), source: 'todo' };
|
|
399
|
+
}
|
|
400
|
+
return { text: firstSentence(b.blocks), source: 'blocks' };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The consolidated decision sheet — every user-owned decision in ONE artefact.
|
|
405
|
+
*
|
|
406
|
+
* `road-to-estate-drawdown` Phase 0 exists because thirteen reading assignments
|
|
407
|
+
* spread across thirteen files did not happen once. The sheet is the whole of
|
|
408
|
+
* the human's contribution to that campaign, front-loaded: one document, sorted
|
|
409
|
+
* by how much each answer unblocks, with a default per item so that
|
|
410
|
+
* accept-all-defaults is a valid answer.
|
|
411
|
+
*
|
|
412
|
+
* This differs from `--all` in the two ways that matter for that job. `--all`
|
|
413
|
+
* renders every blocker grouped by owner and is a terminal view; the sheet is
|
|
414
|
+
* user-owned ONLY, and every row carries a `Default:` plus the PROVENANCE of
|
|
415
|
+
* that default. Provenance is the load-bearing column: a sheet that presented an
|
|
416
|
+
* agent-drafted default beside a maintainer-recorded one as the same thing would
|
|
417
|
+
* invite accept-all over exactly the items nobody has examined.
|
|
418
|
+
*
|
|
419
|
+
* No population figure is quoted here on purpose. The first version cited
|
|
420
|
+
* "14 of 21 record a `Recommendation:`, 7 do not" as the justification, and the
|
|
421
|
+
* change that added this renderer then drafted six of those seven — so the
|
|
422
|
+
* docstring was contradicted by the sheet committed beside it (R2 finding).
|
|
423
|
+
* The split is rendered in the sheet's own header from the live tree, which is
|
|
424
|
+
* the only place it cannot go stale.
|
|
425
|
+
*
|
|
426
|
+
* Deterministic by construction — the generator never invents a default. Where
|
|
427
|
+
* none is recorded it says so and names what the reader must supply, so
|
|
428
|
+
* regenerating the sheet cannot silently overwrite an answer with a guess.
|
|
429
|
+
*/
|
|
430
|
+
function renderSheet(entries: readonly Entry[], now: Date): string {
|
|
431
|
+
const mine = entries.filter((e) => needsUser(e.blocker.owner));
|
|
432
|
+
const stamp = now.toISOString().slice(0, 10);
|
|
433
|
+
if (mine.length === 0) {
|
|
434
|
+
return (
|
|
435
|
+
`# Consolidated decision sheet — nothing owned by you (${stamp})\n\n` +
|
|
436
|
+
'No open blocker in the active roadmap tree carries `Owner: user`. Nothing to answer.\n'
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const noRec = mine.filter((e) => e.blocker.recommendation.trim() === '');
|
|
441
|
+
const legacyNotes = noRec.filter((e) => isLegacy(e.blocker)).length;
|
|
442
|
+
const missing = noRec.length - legacyNotes;
|
|
443
|
+
const drafted = mine.filter((e) => isAgentDrafted(e.blocker.recommendation)).length;
|
|
444
|
+
const maintainerRecorded = mine.length - noRec.length - drafted;
|
|
445
|
+
const out: string[] = [
|
|
446
|
+
`# Consolidated decision sheet — ${String(mine.length)} decisions owned by you`,
|
|
447
|
+
'',
|
|
448
|
+
`> Generated ${stamp} by \`agent-config gates --sheet\` over \`agents/roadmaps/\`.`,
|
|
449
|
+
'> Sorted by unblock count, descending. **Accept-all-defaults is a valid answer**',
|
|
450
|
+
'> (`road-to-estate-drawdown` blocker `b-consolidated-decision-sheet`, option (a));',
|
|
451
|
+
'> answering only the two largest-unblock items and deferring the rest is option (c),',
|
|
452
|
+
'> which the roadmap itself recommends. Whichever you pick, the agent writes the answers',
|
|
453
|
+
'> back into each roadmap at its own blocker — that is not your work.',
|
|
454
|
+
'>',
|
|
455
|
+
`> **Provenance of the ${String(mine.length)} defaults: ${String(maintainerRecorded)} maintainer-recorded ·`,
|
|
456
|
+
`> ${String(drafted)} \`agent-drafted\` · ${String(missing)} with no recommendation at all ·`,
|
|
457
|
+
`> ${String(legacyNotes)} legacy \`> Blocked until …\` note(s) that have no field to carry one.**`,
|
|
458
|
+
'> The distinction is in every row on purpose: an agent-drafted default is the',
|
|
459
|
+
'> least-examined thing on this sheet, and accept-all-defaults would accept those too.',
|
|
460
|
+
'>',
|
|
461
|
+
'> This file is DERIVED — every line above and below comes from the roadmaps themselves,',
|
|
462
|
+
'> so regenerating is deterministic and an answer written into this file would be lost.',
|
|
463
|
+
'> Answers go back into each roadmap at its own blocker; the agent does that.',
|
|
464
|
+
'',
|
|
465
|
+
'| # | Decision | Roadmap | Unblocks | Default source |',
|
|
466
|
+
'|---:|---|---|---:|---|',
|
|
467
|
+
];
|
|
468
|
+
mine.forEach((e, i) => {
|
|
469
|
+
const source =
|
|
470
|
+
e.blocker.recommendation.trim() !== ''
|
|
471
|
+
? isAgentDrafted(e.blocker.recommendation)
|
|
472
|
+
? '`agent-drafted`'
|
|
473
|
+
: 'maintainer-recorded'
|
|
474
|
+
: isLegacy(e.blocker)
|
|
475
|
+
? 'none — legacy note'
|
|
476
|
+
: 'none recorded';
|
|
477
|
+
out.push(
|
|
478
|
+
`| ${String(i + 1)} | ${sheetLabel(e.blocker)} | ${e.roadmapRel} | ` +
|
|
479
|
+
`${String(e.openSteps)} | ${source} |`,
|
|
480
|
+
);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
mine.forEach((e, i) => {
|
|
484
|
+
const q = sheetQuestion(e.blocker);
|
|
485
|
+
const qLabel =
|
|
486
|
+
q.source === 'question'
|
|
487
|
+
? 'recorded `Question:`'
|
|
488
|
+
: q.source === 'todo'
|
|
489
|
+
? 'derived from the first `What to do:` step'
|
|
490
|
+
: 'derived from `Blocks:`';
|
|
491
|
+
const recorded = e.blocker.recommendation.trim();
|
|
492
|
+
out.push(
|
|
493
|
+
'',
|
|
494
|
+
`## ${String(i + 1)} · ${sheetLabel(e.blocker)}`,
|
|
495
|
+
'',
|
|
496
|
+
`- **Roadmap:** ${e.roadmapRel}`,
|
|
497
|
+
`- **Unblocks:** ${String(e.openSteps)} open step(s) — ${e.blocker.blocks || '(not recorded)'}`,
|
|
498
|
+
`- **Question** (${qLabel})**:** ${q.text || '(the blocker records no question, no steps and no Blocks: field)'}`,
|
|
499
|
+
);
|
|
500
|
+
if (recorded === '' && isLegacy(e.blocker)) {
|
|
501
|
+
// A legacy `> Blocked until …` note is not a `### blocker:` entry and
|
|
502
|
+
// has no field to carry a recommendation. Saying "agent-drafted, to
|
|
503
|
+
// be written in" would point at a slot that does not exist, so the
|
|
504
|
+
// row names the actual next action instead.
|
|
505
|
+
out.push(
|
|
506
|
+
'- **Default:** _this is a legacy `> Blocked until …` note, not a `### blocker:` entry,' +
|
|
507
|
+
' so it has no `Recommendation:` field to read. Converting it into a real blocker' +
|
|
508
|
+
' entry is what gives it a default._',
|
|
509
|
+
'- **Default source:** none — legacy note',
|
|
510
|
+
);
|
|
511
|
+
} else if (recorded === '') {
|
|
512
|
+
out.push(
|
|
513
|
+
'- **Default:** _none recorded — needs an agent-drafted default before this row can be' +
|
|
514
|
+
' answered by accepting it._',
|
|
515
|
+
'- **Default source:** `agent-drafted` (to be written in below, and marked as such)',
|
|
516
|
+
);
|
|
517
|
+
} else {
|
|
518
|
+
const drafted = isAgentDrafted(recorded);
|
|
519
|
+
out.push(
|
|
520
|
+
`- **Default:** ${firstSentence(recorded)}`,
|
|
521
|
+
drafted
|
|
522
|
+
? '- **Default source:** `agent-drafted` — written into the roadmap’s ' +
|
|
523
|
+
'`Recommendation:` field and marked there, NOT a maintainer decision'
|
|
524
|
+
: '- **Default source:** maintainer-recorded `Recommendation:` in the roadmap',
|
|
525
|
+
`- **Recommendation (full):** ${recorded.replace(/\s+/g, ' ')}`,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (e.blocker.ifNothing.trim() !== '') {
|
|
529
|
+
out.push(`- **If you do nothing:** ${e.blocker.ifNothing.replace(/\s+/g, ' ')}`);
|
|
530
|
+
}
|
|
531
|
+
out.push(
|
|
532
|
+
`- **Done when:** ${e.blocker.resolvedWhen.replace(/\s+/g, ' ') || '(not recorded)'}`,
|
|
533
|
+
'- **Your answer:** _(accept default · override · defer)_',
|
|
534
|
+
);
|
|
535
|
+
});
|
|
536
|
+
out.push('');
|
|
537
|
+
return out.join('\n');
|
|
538
|
+
}
|
|
539
|
+
|
|
345
540
|
function renderReply(entries: readonly Entry[]): string {
|
|
346
541
|
const mine = entries.filter((e) => needsUser(e.blocker.owner));
|
|
347
542
|
if (mine.length === 0) {
|
|
@@ -564,6 +759,7 @@ function main(argv?: readonly string[]): number {
|
|
|
564
759
|
const json = args.includes('--json');
|
|
565
760
|
const reply = args.includes('--reply');
|
|
566
761
|
const pending = args.includes('--pending');
|
|
762
|
+
const sheet = args.includes('--sheet');
|
|
567
763
|
|
|
568
764
|
// `--execute <id>` is answered FIRST, and it is the only mode that writes.
|
|
569
765
|
// It used to sit after the `--pending` early return, so `--pending
|
|
@@ -643,13 +839,20 @@ function main(argv?: readonly string[]): number {
|
|
|
643
839
|
// `--reply` deliberately does not carry the probe: ADR-222 fixes that form
|
|
644
840
|
// at exactly ONE decision rendered in full, and a fired resume condition is
|
|
645
841
|
// not a decision the reader owes anybody — it is a file that can move.
|
|
646
|
-
|
|
842
|
+
// `--reply` and `--sheet` deliberately do not carry the probe: ADR-222 fixes
|
|
843
|
+
// the reply form at exactly ONE decision rendered in full, and a fired resume
|
|
844
|
+
// condition is not a decision the reader owes anybody — it is a file that can
|
|
845
|
+
// move. The sheet is the same argument for the same reason: it is the set of
|
|
846
|
+
// answers the user owes, and a movable file is not one of them.
|
|
847
|
+
const resumed = reply || sheet ? [] : probeLater(roadmapRoot, repoRoot);
|
|
647
848
|
process.stdout.write(
|
|
648
|
-
|
|
649
|
-
?
|
|
650
|
-
:
|
|
651
|
-
?
|
|
652
|
-
:
|
|
849
|
+
sheet
|
|
850
|
+
? renderSheet(entries, new Date())
|
|
851
|
+
: reply
|
|
852
|
+
? renderReply(entries)
|
|
853
|
+
: json
|
|
854
|
+
? renderJson(entries, all, resumed)
|
|
855
|
+
: render(entries, all, resumed),
|
|
653
856
|
);
|
|
654
857
|
return 0;
|
|
655
858
|
}
|
|
@@ -687,6 +890,7 @@ export {
|
|
|
687
890
|
renderJson,
|
|
688
891
|
renderReply,
|
|
689
892
|
renderResumed,
|
|
893
|
+
renderSheet,
|
|
690
894
|
renderPending,
|
|
691
895
|
renderPendingJson,
|
|
692
896
|
};
|
|
@@ -293,8 +293,8 @@ After the last step of a roadmap is done, check completion status:
|
|
|
293
293
|
|---|---|---|---|---|
|
|
294
294
|
| ≥ 1 | 0 | 0 | 0 | **Auto-archive** (silent) — pure completion |
|
|
295
295
|
| ≥ 1 | 0 | 0 | ≥ 1 | **Auto-archive** (silent) — done with explicit drops |
|
|
296
|
-
| ≥ 1 | 0 | ≥ 1 | ≥ 0 | **STOP — Iron Law 3 flow.** Surface deferred items, present
|
|
297
|
-
| 0 | 0 | ≥ 1 | ≥ 0 | **STOP — Iron Law 3 flow.** Was this a scope-drop or deferred-to-later? Same options as 4b. |
|
|
296
|
+
| ≥ 1 | 0 | ≥ 1 | ≥ 0 | **STOP — Iron Law 3 flow.** Surface deferred items, present the options, resolve by the preservation test (council where the item survives, user where it does not). See step 4b. |
|
|
297
|
+
| 0 | 0 | ≥ 1 | ≥ 0 | **STOP — Iron Law 3 flow.** Was this a scope-drop or deferred-to-later? Same options and same routing as 4b. |
|
|
298
298
|
| 0 | 0 | 0 | ≥ 1 | **Auto-skip** (silent) — no work, all cancelled |
|
|
299
299
|
| ≥ 0 | ≥ 1 | ≥ 0 | ≥ 0 | **Ask the user** — open work remains (step 4a) |
|
|
300
300
|
|
|
@@ -343,8 +343,13 @@ After the last step of a roadmap is done, check completion status:
|
|
|
343
343
|
the active tree.
|
|
344
344
|
|
|
345
345
|
4b. **Deferred items present (`count_deferred ≥ 1`, `count_open == 0`)** — Iron Law 3 flow.
|
|
346
|
-
|
|
347
|
-
|
|
346
|
+
Archive **blocked** until resolved. WHO resolves is the preservation test in
|
|
347
|
+
[`roadmap-progress-sync § Who resolves it`](../../rules/roadmap-progress-sync.md),
|
|
348
|
+
never the mode — same menu either way, only the resolver differs:
|
|
349
|
+
|
|
350
|
+
- **1, 2, 4** keep the item alive → **council**, recorded at the item (2 only
|
|
351
|
+
if the follow-up lands in the SAME change).
|
|
352
|
+
- **3, 5** drop or weaken it → **user**, always. In doubt: user.
|
|
348
353
|
|
|
349
354
|
```
|
|
350
355
|
📋 Roadmap closure check — deferred items must resolve before archive:
|
|
@@ -507,6 +507,68 @@ telemetry:
|
|
|
507
507
|
# Append-only JSONL log. Path is relative to the project root.
|
|
508
508
|
# Always gitignored (see config/gitignore-block.txt).
|
|
509
509
|
path: .agent-engagement.jsonl
|
|
510
|
+
# --- Org telemetry (road-to-org-telemetry Phase 1) ---
|
|
511
|
+
#
|
|
512
|
+
# Class-A usage records: which skill was invoked, on which host, at which
|
|
513
|
+
# package version and rule tier, under a pseudonymous user hash and an
|
|
514
|
+
# hour-bucketed timestamp. Zero bytes of project content — the record type
|
|
515
|
+
# has no field capable of holding free text.
|
|
516
|
+
#
|
|
517
|
+
# `enabled: true` on its own does NOT switch this on. All three of
|
|
518
|
+
# `endpoint`, `org_id`, and `salt` must also carry a value, and none of
|
|
519
|
+
# them has a default. That is deliberate: this repository is public, so it
|
|
520
|
+
# ships the key NAMES and no values, and a clone cannot reach the write
|
|
521
|
+
# path by copying the tree. The values come from an org pack.
|
|
522
|
+
#
|
|
523
|
+
# Phase 1 writes records LOCALLY and nothing else. The outbound flush is
|
|
524
|
+
# Phase 2 and org-wide enablement is Phase 3; both are gated on open
|
|
525
|
+
# blockers in the roadmap.
|
|
526
|
+
remote:
|
|
527
|
+
# Master switch. Combined with the three required fields below — see
|
|
528
|
+
# `active` in scripts/telemetry/settings.ts.
|
|
529
|
+
enabled: false
|
|
530
|
+
# Where Phase 2 will flush to. Recorded now, never called in Phase 1.
|
|
531
|
+
endpoint: ""
|
|
532
|
+
# The org whose pack enabled this install; written into every record.
|
|
533
|
+
org_id: ""
|
|
534
|
+
# Org-pack secret. Salts the user and session hashes so a digest of a
|
|
535
|
+
# login name is not reversible by dictionary. Never written into a
|
|
536
|
+
# record, never logged.
|
|
537
|
+
#
|
|
538
|
+
# It must live in the SAME `.agent-settings.yml` the reader resolves —
|
|
539
|
+
# `read_remote_settings` opens one file and consults no override layer,
|
|
540
|
+
# so a salt placed in `.agent-settings.local.yml` leaves the install
|
|
541
|
+
# silently inactive. Keep that file out of version control, or supply it
|
|
542
|
+
# from the org pack at install time.
|
|
543
|
+
salt: ""
|
|
544
|
+
# `session-end` = flush at session end (Phase 2). `never` = no transport,
|
|
545
|
+
# which is a legitimate way to run the local half while the sink question
|
|
546
|
+
# is still open.
|
|
547
|
+
#
|
|
548
|
+
# `never` is NOT an unbounded steady state: the retention budget below
|
|
549
|
+
# bounds the log whether or not a flush is configured. The consequence
|
|
550
|
+
# worth knowing before choosing `never` is that the local file is then the
|
|
551
|
+
# only store, so a record retention evicts is a record that is never sent
|
|
552
|
+
# anywhere. Raise `retention.max_age_days` if you need a longer local
|
|
553
|
+
# history.
|
|
554
|
+
flush: session-end
|
|
555
|
+
# Growth budget for the log below (`scale-discipline` R-A7). Enforced by
|
|
556
|
+
# the writer itself, so there is no sweep to remember to run: after each
|
|
557
|
+
# append, records older than `max_age_days` are dropped, and the file is
|
|
558
|
+
# held under `max_bytes` from the oldest end regardless of dates.
|
|
559
|
+
#
|
|
560
|
+
# The defaults are measured, not guessed. The busiest machine this tree
|
|
561
|
+
# has observed writes 6.6 skill invocations a day at ~270 bytes each, so
|
|
562
|
+
# 90 days is roughly 600 records (~160 KiB) and is the cap that actually
|
|
563
|
+
# binds; 2 MiB is the backstop for a rate that has not been observed.
|
|
564
|
+
# Omit the section to take both defaults — there is no way to configure
|
|
565
|
+
# "unbounded", which is the state R-A7 forbids.
|
|
566
|
+
retention:
|
|
567
|
+
max_age_days: 90
|
|
568
|
+
max_bytes: 2097152
|
|
569
|
+
output:
|
|
570
|
+
# Append-only JSONL log, relative to the project root. Gitignored.
|
|
571
|
+
path: .agent-telemetry.jsonl
|
|
510
572
|
|
|
511
573
|
# --- Linked projects (cross-repo awareness, local-only) ---
|
|
512
574
|
#
|
|
@@ -602,6 +664,14 @@ the canonical narrative lives in
|
|
|
602
664
|
| `telemetry.artifact_engagement.record.consulted` | `true`, `false` | `true` | When `true`: record artefacts loaded into context. |
|
|
603
665
|
| `telemetry.artifact_engagement.record.applied` | `true`, `false` | `true` | When `true`: record artefacts cited or driving a decision. |
|
|
604
666
|
| `telemetry.artifact_engagement.output.path` | path | `.agent-engagement.jsonl` | Append-only JSONL log path, relative to the project root. Always gitignored. |
|
|
667
|
+
| `telemetry.remote.enabled` | `true`, `false` | `false` | Opt-in for Class-A org usage records. **Not the switch on its own** — `endpoint`, `org_id`, and `salt` must all carry a value too, and none has a default, so a clone of the public repository cannot reach the write path. Not in the shipped template; a missing section means disabled. |
|
|
668
|
+
| `telemetry.remote.endpoint` | URL | *(none)* | Where Phase 2 will flush records to. Recorded now; no outbound call exists in Phase 1. Empty ⇒ inactive. |
|
|
669
|
+
| `telemetry.remote.org_id` | string | *(none)* | The org whose pack enabled this install; written into every record. Empty ⇒ inactive. |
|
|
670
|
+
| `telemetry.remote.salt` | string | *(none)* | Org-pack secret salting the user and session hashes — without it a digest of a login name is dictionary-reversible. Never written into a record, never logged. Must sit in the same `.agent-settings.yml` the reader resolves: it consults no override layer, so a salt in `.agent-settings.local.yml` leaves the install silently inactive. Empty ⇒ inactive. |
|
|
671
|
+
| `telemetry.remote.flush` | `session-end`, `never` | `session-end` | When Phase 2 flushes. `never` means no transport — a legitimate way to run the local half while the sink question is open. It is **not** an unbounded steady state: `retention` below bounds the log either way. Under `never` the local file is the only store, so a record retention evicts is never sent anywhere. |
|
|
672
|
+
| `telemetry.remote.retention.max_age_days` | positive integer | `90` | TTL for the local log (`scale-discipline` R-A7). Records older than this are dropped after an append. At the observed 6.6 invocations/day this is ~600 records (~160 KiB) and is the cap that actually binds. Zero, negative, and non-integer values fall back to the default — there is no way to configure "unbounded". |
|
|
673
|
+
| `telemetry.remote.retention.max_bytes` | positive integer | `2097152` (2 MiB) | Hard ceiling on the local log regardless of record age; the oldest records are dropped first, back to 75 % of the cap so the next prune is not immediate. Backstop for a write rate this tree has not observed. |
|
|
674
|
+
| `telemetry.remote.output.path` | path | `.agent-telemetry.jsonl` | Append-only JSONL log path, relative to the project root. Gitignored. Bounded by `retention` above. |
|
|
605
675
|
| `linked_projects` | list of `{path, include}` | `[]` | IDE-attached sibling repos in scope for proactive cross-repo awareness. **Belongs in `.agent-settings.local.yml` (in agents/settings/)** (per-machine, gitignored). See [cross-repo guide](../../docs/guides/cross-repo-linked-projects.md) + ADR-032. |
|
|
606
676
|
| `linked_projects_max_files` | integer | `20000` | File-count ceiling above which a detected sibling is flagged `large` (awareness only). Never excludes. |
|
|
607
677
|
| `knowledge.global_sharing.enabled` | `true`, `false` | `true` | **User-global** (keep in `~/.event4u/agent-config/agent-settings.yml`). Master switch for the file-first global knowledge-card store (ADR-100; default-ON per ADR-119, the council-validated bounded-downside flip superseding ADR-103 — adversarially spot-checked redaction incl. hidden-unicode hardening, narrowest tier default, pre-registered demotion trigger). `false` fully no-ops the layer (single-key revert); v1 project-local cards unaffected. |
|
|
@@ -39,7 +39,7 @@ schema_version: 1
|
|
|
39
39
|
# CI guard: a release bump of `package.json` must update this value
|
|
40
40
|
# in lockstep — see scripts/check_template_pin_drift.ts (road-to-
|
|
41
41
|
# portable-runtime-and-update-check P3.3).
|
|
42
|
-
agent_config_version: "14.
|
|
42
|
+
agent_config_version: "14.4.0"
|
|
43
43
|
|
|
44
44
|
# --- Project identity ---
|
|
45
45
|
project:
|