@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
|
@@ -32,8 +32,12 @@
|
|
|
32
32
|
*
|
|
33
33
|
* Invocation (from project root):
|
|
34
34
|
* node node_modules/.bin/tsx .augment/scripts/update_roadmap_progress.ts # rewrite
|
|
35
|
+
* node node_modules/.bin/tsx .augment/scripts/update_roadmap_progress.ts --archive # rewrite + archive completed
|
|
35
36
|
* node node_modules/.bin/tsx .augment/scripts/update_roadmap_progress.ts --check # CI: exit 1 if stale
|
|
36
37
|
*
|
|
38
|
+
* `--archive` is opt-in and `--check` refuses it; the full contract, the
|
|
39
|
+
* council record and the sweep spawner live in `archival_sweep.ts`.
|
|
40
|
+
*
|
|
37
41
|
* --- Parity notes (ADR-200) ---
|
|
38
42
|
*
|
|
39
43
|
* - Python `round()` is banker's rounding (round-half-to-even). Percentages and
|
|
@@ -59,6 +63,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
59
63
|
import * as fs from 'node:fs';
|
|
60
64
|
import * as path from 'node:path';
|
|
61
65
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
66
|
+
import { run_archival_sweep } from './archival_sweep.js';
|
|
62
67
|
import type * as YamlModule from 'yaml';
|
|
63
68
|
|
|
64
69
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -280,12 +285,21 @@ function is_draft(fm: Record<string, string>): boolean {
|
|
|
280
285
|
return DRAFT_VALUES.has((fm['status'] ?? '').toLowerCase());
|
|
281
286
|
}
|
|
282
287
|
|
|
283
|
-
|
|
284
|
-
|
|
288
|
+
/**
|
|
289
|
+
* The NAME half of the candidate test. Split out because the parked inventory
|
|
290
|
+
* reads `later/` on purpose and `is_roadmap_candidate` rejects any path carrying
|
|
291
|
+
* an excluded component — `later` included. Sharing this half keeps
|
|
292
|
+
* `later/README.md` excluded from both sides by one rule rather than two.
|
|
293
|
+
*/
|
|
294
|
+
function is_roadmap_name(name: string): boolean {
|
|
285
295
|
if (EXCLUDE_NAMES.has(name)) {
|
|
286
296
|
return false;
|
|
287
297
|
}
|
|
288
|
-
|
|
298
|
+
return !EXCLUDE_PREFIXES.some((pre) => name.startsWith(pre));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function is_roadmap_candidate(p: string): boolean {
|
|
302
|
+
if (!is_roadmap_name(path.basename(p))) {
|
|
289
303
|
return false;
|
|
290
304
|
}
|
|
291
305
|
// path.parts — every component of the path; exclude if any is an excluded dir.
|
|
@@ -741,6 +755,140 @@ function collect(roadmap_root: string): RoadmapStats[] {
|
|
|
741
755
|
return results;
|
|
742
756
|
}
|
|
743
757
|
|
|
758
|
+
/** One parked roadmap, as the inventory needs it. */
|
|
759
|
+
interface ParkedRoadmap {
|
|
760
|
+
rel: string;
|
|
761
|
+
resume: string | null;
|
|
762
|
+
/** Only the linter's looser marker matched — CI passes, the cell cannot quote. */
|
|
763
|
+
unlabelled: boolean;
|
|
764
|
+
/** Open blockers still carried by the parked file. */
|
|
765
|
+
open_blockers: number;
|
|
766
|
+
/** Of those, the ones whose owner is the user. */
|
|
767
|
+
needs_user: number;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* A line that STATES when the roadmap comes back. Two tiers exist because
|
|
772
|
+
* `lint_roadmap_later_disposition` accepts a bare `trigger`, which is too loose to
|
|
773
|
+
* quote from — measured over the live tree it matched a `Source:` path containing
|
|
774
|
+
* `mixed-trigger-cleanup`, producing cells that were wrong rather than short.
|
|
775
|
+
*/
|
|
776
|
+
const RESUME_STATEMENT = /\b(blocked until|resume when|resume-when|blocked-until)\b/i;
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* The linter's looser vocabulary. A file matching only this DOES carry a resume
|
|
780
|
+
* condition as far as CI is concerned, so reporting it as missing would make the
|
|
781
|
+
* dashboard contradict a gate that already passed.
|
|
782
|
+
*/
|
|
783
|
+
const RESUME_LOOSE = /\btrigger\b/i;
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Parked roadmaps, with the resume condition each one records.
|
|
787
|
+
*
|
|
788
|
+
* A view over existing data, NOT a new governance mechanism: nothing here gates,
|
|
789
|
+
* counts toward a ratchet, or changes what `collect()` scans. It exists because
|
|
790
|
+
* `later/` is excluded from `collect()` and from `/roadmap:process-*` — correct,
|
|
791
|
+
* parked work is not backlog — and that exclusion had no counterweight, so a
|
|
792
|
+
* roadmap moved there left the dashboard entirely.
|
|
793
|
+
*/
|
|
794
|
+
function collect_parked(roadmap_root: string): ParkedRoadmap[] {
|
|
795
|
+
const dir = path.join(roadmap_root, 'later');
|
|
796
|
+
let names: string[];
|
|
797
|
+
try {
|
|
798
|
+
names = fs.readdirSync(dir).sort();
|
|
799
|
+
} catch {
|
|
800
|
+
return [];
|
|
801
|
+
}
|
|
802
|
+
const out: ParkedRoadmap[] = [];
|
|
803
|
+
for (const name of names) {
|
|
804
|
+
const full = path.join(dir, name);
|
|
805
|
+
// The NAME half only: the full predicate rejects every path under `later`.
|
|
806
|
+
if (!_isFile(full) || !is_roadmap_name(name)) {
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
let text: string;
|
|
810
|
+
try {
|
|
811
|
+
text = fs.readFileSync(full, { encoding: 'utf-8' });
|
|
812
|
+
} catch {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (is_draft(parse_frontmatter(text))) {
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const cell = resume_cell(text);
|
|
819
|
+
// Parked blockers are still open blockers — counting them here is what stops
|
|
820
|
+
// this section becoming the surface where a park reads as a resolution.
|
|
821
|
+
const open = parse_blockers(text).filter((b) => !blocker_is_resolved(b));
|
|
822
|
+
out.push({
|
|
823
|
+
rel: name,
|
|
824
|
+
resume: cell.stated,
|
|
825
|
+
unlabelled: cell.unlabelled,
|
|
826
|
+
open_blockers: open.length,
|
|
827
|
+
needs_user: open.filter((b) => blocker_needs_user(b.owner)).length,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
return out;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* What brings a parked roadmap back, as one table cell. Both fields false means the
|
|
835
|
+
* file records nothing — said outright, because an empty cell reads as "nothing to
|
|
836
|
+
* wait for".
|
|
837
|
+
*
|
|
838
|
+
* The PARAGRAPH is joined, not the matched line: roadmap prose wraps at ~80
|
|
839
|
+
* columns, and quoting one line ended 39 of 52 cells mid-clause. A cell stopping at
|
|
840
|
+
* "the pair for" is not a shorter sentence, it is a different claim.
|
|
841
|
+
*
|
|
842
|
+
* Both tiers are tested over the SAME lines. Testing the loose one whole-file while
|
|
843
|
+
* the strict one ran per line made `unlabelled` fire on any file using the word
|
|
844
|
+
* "trigger" anywhere.
|
|
845
|
+
*/
|
|
846
|
+
function resume_cell(text: string): { stated: string | null; unlabelled: boolean } {
|
|
847
|
+
const lines = text.split('\n');
|
|
848
|
+
for (let i = 0; i < lines.length; i++) {
|
|
849
|
+
const raw = lines[i] as string;
|
|
850
|
+
if (!RESUME_STATEMENT.test(raw)) {
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
const parts: string[] = [raw];
|
|
854
|
+
// Continuation lines of the same paragraph: non-blank, and not the start of
|
|
855
|
+
// a new block (list item, heading, fence, table row).
|
|
856
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
857
|
+
const next = (lines[j] as string).replace(/^\s*>\s?/, '').trim();
|
|
858
|
+
if (next === '' || /^([-*+]\s|#{1,6}\s|```|\||>\s*$)/.test(next)) {
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
parts.push(next);
|
|
862
|
+
}
|
|
863
|
+
const joined = cell_text(parts.join(' '));
|
|
864
|
+
if (joined === '') {
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
return { stated: joined, unlabelled: false };
|
|
868
|
+
}
|
|
869
|
+
return { stated: null, unlabelled: lines.some((l) => RESUME_LOOSE.test(l)) };
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* One roadmap sentence, safe between two pipes. ORDER IS LOAD-BEARING: truncate
|
|
874
|
+
* before escaping, because cutting an escaped string severs a `\|` and leaves a
|
|
875
|
+
* dangling backslash. HTML comments are removed, not escaped — a `<!--` reaching a
|
|
876
|
+
* cell comments out the rest of the document, and one already did.
|
|
877
|
+
*/
|
|
878
|
+
function cell_text(raw: string): string {
|
|
879
|
+
let out = raw
|
|
880
|
+
.replace(/^\s*>\s?/, '')
|
|
881
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
882
|
+
.replace(/<!--|-->/g, '')
|
|
883
|
+
.replace(/\*\*/g, '')
|
|
884
|
+
.replace(/\s+/g, ' ')
|
|
885
|
+
.trim();
|
|
886
|
+
if (out.length > 200) {
|
|
887
|
+
out = `${out.slice(0, 197).trimEnd()}...`;
|
|
888
|
+
}
|
|
889
|
+
return out.replace(/\|/g, '\\|');
|
|
890
|
+
}
|
|
891
|
+
|
|
744
892
|
function unarchived_complete(roadmaps: RoadmapStats[]): RoadmapStats[] {
|
|
745
893
|
return roadmaps.filter((r) => r.total_active > 0 && r.open_ === 0 && r.deferred === 0);
|
|
746
894
|
}
|
|
@@ -812,7 +960,7 @@ function collect_bundles(repo_root: string): Bundle[] {
|
|
|
812
960
|
return out;
|
|
813
961
|
}
|
|
814
962
|
|
|
815
|
-
function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null
|
|
963
|
+
function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null, roadmap_root: string): string {
|
|
816
964
|
const total_done = roadmaps.reduce((s, r) => s + r.done, 0);
|
|
817
965
|
const total_active = roadmaps.reduce((s, r) => s + r.total_active, 0);
|
|
818
966
|
const overall_pct = total_active ? _pyRound((total_done * 100) / total_active) : 0;
|
|
@@ -836,6 +984,7 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
|
|
|
836
984
|
'[skipped/](roadmaps/skipped/) · [later/](roadmaps/later/)' +
|
|
837
985
|
(total_open_blockers > 0
|
|
838
986
|
? ` · **${total_open_blockers}** open blocker${total_open_blockers !== 1 ? 's' : ''}` +
|
|
987
|
+
' in the active tree' +
|
|
839
988
|
(user_open_blockers > 0
|
|
840
989
|
? `, **${user_open_blockers}** need you → \`agent-config gates\``
|
|
841
990
|
: '')
|
|
@@ -939,6 +1088,58 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
|
|
|
939
1088
|
});
|
|
940
1089
|
lines.push('');
|
|
941
1090
|
lines.push('---\n');
|
|
1091
|
+
|
|
1092
|
+
// Listed under the active table so the two are read together. Parked work
|
|
1093
|
+
// carries no counts here; what a reader needs is what brings it back.
|
|
1094
|
+
const parked = collect_parked(roadmap_root);
|
|
1095
|
+
if (parked.length > 0) {
|
|
1096
|
+
lines.push(
|
|
1097
|
+
`## Parked — \`later/\` (${parked.length} roadmap` +
|
|
1098
|
+
`${parked.length !== 1 ? 's' : ''}, not active backlog)\n`,
|
|
1099
|
+
);
|
|
1100
|
+
lines.push(
|
|
1101
|
+
'> Excluded from the table above and from `/roadmap:process-*` by ' +
|
|
1102
|
+
'design. Listed here so a resume condition is visible without ' +
|
|
1103
|
+
'opening the file.\n',
|
|
1104
|
+
);
|
|
1105
|
+
const parked_open = parked.reduce((n, r) => n + r.open_blockers, 0);
|
|
1106
|
+
const parked_user = parked.reduce((n, r) => n + r.needs_user, 0);
|
|
1107
|
+
if (parked_open > 0) {
|
|
1108
|
+
lines.push(
|
|
1109
|
+
`> Carrying **${parked_open}** open blocker` +
|
|
1110
|
+
`${parked_open !== 1 ? 's' : ''}` +
|
|
1111
|
+
(parked_user > 0 ? `, **${parked_user}** owned by you` : '') +
|
|
1112
|
+
' — parking resolves nothing, so these are NOT in the ' +
|
|
1113
|
+
'active-tree count above.\n',
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
lines.push('| Roadmap | Open blockers | Resume when |');
|
|
1117
|
+
lines.push('|---|---:|---|');
|
|
1118
|
+
for (const r of parked) {
|
|
1119
|
+
const cell =
|
|
1120
|
+
r.resume ??
|
|
1121
|
+
(r.unlabelled
|
|
1122
|
+
? '_condition present but unlabelled — see file_'
|
|
1123
|
+
: '_no resume line recorded_');
|
|
1124
|
+
const blockers =
|
|
1125
|
+
r.open_blockers === 0
|
|
1126
|
+
? '0'
|
|
1127
|
+
: `${r.open_blockers}${r.needs_user > 0 ? ` (${r.needs_user} you)` : ''}`;
|
|
1128
|
+
// `<!-- ref-ignore -->` per row — a correctness fix, not a suppression. A
|
|
1129
|
+
// resume condition NAMES WHAT DOES NOT EXIST YET ("when `x.md` exists" is
|
|
1130
|
+
// the commonest shape in `later/`), so checking these quoted paths as live
|
|
1131
|
+
// references fires the gate on correct content. It fired immediately. The
|
|
1132
|
+
// authoritative copy of each path is the roadmap file; this table quotes
|
|
1133
|
+
// it, and that gate does not walk `later/` either way.
|
|
1134
|
+
lines.push(
|
|
1135
|
+
`| [${r.rel}](roadmaps/later/${r.rel}) | ${blockers} | ${cell} |` +
|
|
1136
|
+
' <!-- ref-ignore -->',
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
lines.push('');
|
|
1140
|
+
lines.push('---\n');
|
|
1141
|
+
}
|
|
1142
|
+
|
|
942
1143
|
lines.push('## Per-roadmap phase breakdown\n');
|
|
943
1144
|
for (const r of roadmaps) {
|
|
944
1145
|
lines.push(`### [${r.rel}](roadmaps/${r.rel})\n`);
|
|
@@ -1013,16 +1214,18 @@ class ArgparseExit extends Error {
|
|
|
1013
1214
|
const _PROG = 'update_roadmap_progress.py';
|
|
1014
1215
|
|
|
1015
1216
|
function _usage(): string {
|
|
1016
|
-
return `usage: ${_PROG} [-h] [--check] [--repo-root REPO_ROOT]\n`;
|
|
1217
|
+
return `usage: ${_PROG} [-h] [--check] [--archive | --no-archive] [--repo-root REPO_ROOT]\n`;
|
|
1017
1218
|
}
|
|
1018
1219
|
|
|
1019
1220
|
interface Args {
|
|
1020
1221
|
check: boolean;
|
|
1222
|
+
archive: boolean;
|
|
1021
1223
|
repo_root: string;
|
|
1022
1224
|
}
|
|
1023
1225
|
|
|
1024
1226
|
function _parseArgs(argv: readonly string[]): Args {
|
|
1025
1227
|
let check = false;
|
|
1228
|
+
let archive = false;
|
|
1026
1229
|
let repo_root = process.cwd();
|
|
1027
1230
|
const emitError = (msg: string): never => {
|
|
1028
1231
|
process.stderr.write(_usage());
|
|
@@ -1038,6 +1241,12 @@ function _parseArgs(argv: readonly string[]): Args {
|
|
|
1038
1241
|
} else if (tok === '--check') {
|
|
1039
1242
|
check = true;
|
|
1040
1243
|
i += 1;
|
|
1244
|
+
} else if (tok === '--archive') {
|
|
1245
|
+
archive = true;
|
|
1246
|
+
i += 1;
|
|
1247
|
+
} else if (tok === '--no-archive') {
|
|
1248
|
+
archive = false;
|
|
1249
|
+
i += 1;
|
|
1041
1250
|
} else if (tok === '--repo-root') {
|
|
1042
1251
|
const val = argv[i + 1];
|
|
1043
1252
|
if (val === undefined) {
|
|
@@ -1052,7 +1261,13 @@ function _parseArgs(argv: readonly string[]): Args {
|
|
|
1052
1261
|
emitError(`unrecognized arguments: ${tok}`);
|
|
1053
1262
|
}
|
|
1054
1263
|
}
|
|
1055
|
-
|
|
1264
|
+
if (check && archive) {
|
|
1265
|
+
// A gate that mutates the tree it is checking cannot be trusted by CI
|
|
1266
|
+
// (council 2026-08-20, both seats). Refuse the combination outright
|
|
1267
|
+
// rather than silently letting one win.
|
|
1268
|
+
emitError('argument --archive: not allowed with --check');
|
|
1269
|
+
}
|
|
1270
|
+
return { check, archive, repo_root };
|
|
1056
1271
|
}
|
|
1057
1272
|
|
|
1058
1273
|
/**
|
|
@@ -1094,8 +1309,31 @@ function main(argv?: readonly string[]): number {
|
|
|
1094
1309
|
process.stdout.write(`ℹ️ No roadmaps directory at ${roadmap_root} — nothing to do.\n`);
|
|
1095
1310
|
return 0;
|
|
1096
1311
|
}
|
|
1312
|
+
// Archive before rendering, so the dashboard describes the tree the sweep
|
|
1313
|
+
// leaves behind rather than the one it found. `--check` never archives: a
|
|
1314
|
+
// gate that mutates the tree it is checking cannot be trusted by CI.
|
|
1315
|
+
let sweep_out = '';
|
|
1316
|
+
if (!args.check && args.archive && unarchived_complete(collect(roadmap_root)).length > 0) {
|
|
1317
|
+
const swept = run_archival_sweep(repo_root);
|
|
1318
|
+
sweep_out = swept.stdout;
|
|
1319
|
+
if (swept.stderr) {
|
|
1320
|
+
process.stderr.write(swept.stderr);
|
|
1321
|
+
}
|
|
1322
|
+
if (!swept.ok) {
|
|
1323
|
+
// A half-finished sweep leaves the tree in a shape the dashboard
|
|
1324
|
+
// would describe as if it were intentional. Report and stop before
|
|
1325
|
+
// writing (council 2026-08-20: "archival failures must prevent
|
|
1326
|
+
// dashboard rendering so partially updated state is not presented
|
|
1327
|
+
// as current").
|
|
1328
|
+
process.stderr.write(
|
|
1329
|
+
'❌ The archival sweep failed — dashboard NOT regenerated. ' +
|
|
1330
|
+
'Fix the tree, then re-run.\n',
|
|
1331
|
+
);
|
|
1332
|
+
return 1;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1097
1335
|
const roadmaps = collect(roadmap_root);
|
|
1098
|
-
const new_text = render(roadmaps, collect_bundles(repo_root));
|
|
1336
|
+
const new_text = render(roadmaps, collect_bundles(repo_root), roadmap_root);
|
|
1099
1337
|
const current = fs.existsSync(target) ? fs.readFileSync(target, { encoding: 'utf-8' }) : '';
|
|
1100
1338
|
const complete = unarchived_complete(roadmaps);
|
|
1101
1339
|
const pending = pending_iron_law_3(roadmaps);
|
|
@@ -1166,7 +1404,13 @@ function main(argv?: readonly string[]): number {
|
|
|
1166
1404
|
`${roadmaps.length} roadmap(s) · ` +
|
|
1167
1405
|
`${roadmaps.reduce((s, r) => s + r.done, 0)}/${roadmaps.reduce((s, r) => s + r.total_active, 0)} steps done.\n`,
|
|
1168
1406
|
);
|
|
1407
|
+
if (sweep_out) {
|
|
1408
|
+
process.stdout.write(sweep_out);
|
|
1409
|
+
}
|
|
1169
1410
|
if (complete.length) {
|
|
1411
|
+
// Whatever survived the sweep is complete but NOT archivable — the
|
|
1412
|
+
// sweep already said why on stderr (an open blocker outlives its
|
|
1413
|
+
// steps). Without `--archive` this is every complete roadmap.
|
|
1170
1414
|
process.stderr.write(
|
|
1171
1415
|
'⚠️ Completed roadmaps not yet archived — move to ' +
|
|
1172
1416
|
'`agents/roadmaps/archive/`:\n',
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The detached Class-A sender (road-to-org-telemetry Phase 2, step 2.1).
|
|
4
|
+
*
|
|
5
|
+
* Spawned by `spawn_detached_sender` and never by a human. It is the ONLY
|
|
6
|
+
* code in this tree that makes an outbound telemetry call, which is why it is
|
|
7
|
+
* one small file with no imports beyond node builtins: the egress surface
|
|
8
|
+
* should be readable end to end in one screen by anyone auditing what leaves
|
|
9
|
+
* a machine.
|
|
10
|
+
*
|
|
11
|
+
* PLAIN JS, DELIBERATELY. Its siblings here are TypeScript resolved by `tsx`
|
|
12
|
+
* at import time. A detached child inherits no such resolution, so a `.ts`
|
|
13
|
+
* sender would depend on finding a `tsx` binary in a consumer install at
|
|
14
|
+
* session teardown. `process.execPath` — plain node — exists by construction.
|
|
15
|
+
* The cost of that choice is that this file cannot import the sibling
|
|
16
|
+
* modules, so the drain algorithm lives HERE and nowhere else; the TypeScript
|
|
17
|
+
* side owns enqueue and spawn only, and there is no second copy to drift.
|
|
18
|
+
*
|
|
19
|
+
* CLAIM-BY-RENAME, NOT READ-THEN-TRUNCATE. `rename` is atomic within a
|
|
20
|
+
* filesystem, so a session that appends a record while this process is
|
|
21
|
+
* sending writes to a fresh spool rather than into bytes about to be deleted.
|
|
22
|
+
* Read-then-truncate loses exactly those records, silently, and only under
|
|
23
|
+
* concurrency — the failure shape that never shows up in a single-session
|
|
24
|
+
* test.
|
|
25
|
+
*
|
|
26
|
+
* FAILURE KEEPS THE DATA. A non-2xx, a timeout, a DNS failure, a thrown
|
|
27
|
+
* anything: the claimed lines go back in front of the spool and the next
|
|
28
|
+
* session's flush tries again. "Local retention for the next flush" is the
|
|
29
|
+
* step's own wording and this is it. Exit code is always 0 — nothing reads it
|
|
30
|
+
* (`stdio: 'ignore'`), and a non-zero from a detached child is a log line
|
|
31
|
+
* nobody will ever see.
|
|
32
|
+
*/
|
|
33
|
+
import * as fs from 'node:fs';
|
|
34
|
+
|
|
35
|
+
const DEFAULT_TIMEOUT_MS = 1000;
|
|
36
|
+
|
|
37
|
+
function parse_args(argv) {
|
|
38
|
+
const out = { spool: '', endpoint: '', timeout_ms: DEFAULT_TIMEOUT_MS };
|
|
39
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
40
|
+
const a = argv[i];
|
|
41
|
+
if (a === '--spool') {
|
|
42
|
+
out.spool = argv[i + 1] ?? '';
|
|
43
|
+
i += 1;
|
|
44
|
+
} else if (a === '--endpoint') {
|
|
45
|
+
out.endpoint = argv[i + 1] ?? '';
|
|
46
|
+
i += 1;
|
|
47
|
+
} else if (a === '--timeout') {
|
|
48
|
+
const n = Number.parseInt(argv[i + 1] ?? '', 10);
|
|
49
|
+
if (Number.isFinite(n) && n > 0) out.timeout_ms = n;
|
|
50
|
+
i += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Take the spool by rename. Returns the claim path, or `null` when there is
|
|
58
|
+
* nothing to send — including the race where a sibling sender claimed it
|
|
59
|
+
* first, which is a no-op rather than an error.
|
|
60
|
+
*/
|
|
61
|
+
export function claim_spool(spool_path) {
|
|
62
|
+
let size = 0;
|
|
63
|
+
try {
|
|
64
|
+
size = fs.statSync(spool_path).size;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (size === 0) return null;
|
|
69
|
+
const claim = `${spool_path}.sending.${process.pid}.${Date.now()}`;
|
|
70
|
+
try {
|
|
71
|
+
fs.renameSync(spool_path, claim);
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return claim;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Put the claimed lines back in FRONT of whatever arrived meanwhile. */
|
|
79
|
+
export function restore_claim(claim_path, spool_path) {
|
|
80
|
+
try {
|
|
81
|
+
const claimed = fs.readFileSync(claim_path, 'utf-8');
|
|
82
|
+
let current = '';
|
|
83
|
+
try {
|
|
84
|
+
current = fs.readFileSync(spool_path, 'utf-8');
|
|
85
|
+
} catch {
|
|
86
|
+
current = '';
|
|
87
|
+
}
|
|
88
|
+
fs.writeFileSync(spool_path, claimed + current, { encoding: 'utf-8' });
|
|
89
|
+
fs.unlinkSync(claim_path);
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
// The claim file survives under its own name; the next sender's
|
|
93
|
+
// `claim_spool` will not see it, so it is recoverable by hand rather
|
|
94
|
+
// than lost. Better than a partial write over the live spool.
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* POST the batch as NDJSON. Returns true only on a 2xx.
|
|
101
|
+
*
|
|
102
|
+
* The body is the spooled bytes verbatim — every line is a Class-A record
|
|
103
|
+
* whose fields are all structural (see `remote.ts`), so there is no
|
|
104
|
+
* serialisation step here that could introduce a field the record type does
|
|
105
|
+
* not have.
|
|
106
|
+
*/
|
|
107
|
+
export async function post_batch(endpoint, body, timeout_ms) {
|
|
108
|
+
try {
|
|
109
|
+
const res = await fetch(endpoint, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: { 'content-type': 'application/x-ndjson' },
|
|
112
|
+
body,
|
|
113
|
+
signal: AbortSignal.timeout(timeout_ms),
|
|
114
|
+
});
|
|
115
|
+
return res.ok === true;
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function run(args) {
|
|
122
|
+
if (!args.spool || !args.endpoint) return 'no-op';
|
|
123
|
+
const claim = claim_spool(args.spool);
|
|
124
|
+
if (claim === null) return 'no-op';
|
|
125
|
+
|
|
126
|
+
let body = '';
|
|
127
|
+
try {
|
|
128
|
+
body = fs.readFileSync(claim, 'utf-8');
|
|
129
|
+
} catch {
|
|
130
|
+
return 'read-failed';
|
|
131
|
+
}
|
|
132
|
+
if (body.length === 0) {
|
|
133
|
+
try {
|
|
134
|
+
fs.unlinkSync(claim);
|
|
135
|
+
} catch {
|
|
136
|
+
/* nothing to clean */
|
|
137
|
+
}
|
|
138
|
+
return 'no-op';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const ok = await post_batch(args.endpoint, body, args.timeout_ms);
|
|
142
|
+
if (ok) {
|
|
143
|
+
try {
|
|
144
|
+
fs.unlinkSync(claim);
|
|
145
|
+
} catch {
|
|
146
|
+
/* sent; a stale claim is harmless */
|
|
147
|
+
}
|
|
148
|
+
return 'sent';
|
|
149
|
+
}
|
|
150
|
+
restore_claim(claim, args.spool);
|
|
151
|
+
return 'retained';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const _entry = process.argv[1] ?? '';
|
|
155
|
+
const _invoked_directly = _entry !== '' && import.meta.url === new URL(`file://${_entry}`).href;
|
|
156
|
+
|
|
157
|
+
if (_invoked_directly) {
|
|
158
|
+
run(parse_args(process.argv.slice(2)))
|
|
159
|
+
.then(() => process.exit(0))
|
|
160
|
+
.catch(() => process.exit(0));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export { parse_args };
|