@event4u/agent-config 15.0.0 → 16.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +301 -347
- package/CONTRIBUTING.md +9 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +26 -0
- package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
- package/dist/agent-src/commands/challenge-me/closure.md +178 -0
- package/dist/agent-src/commands/challenge-me.md +9 -2
- package/dist/agent-src/commands/council/default.md +25 -5
- package/dist/agent-src/commands/feature/plan.md +28 -1
- package/dist/agent-src/commands/feature/roadmap.md +28 -1
- package/dist/agent-src/commands/implement-ticket.md +27 -0
- package/dist/agent-src/commands/jira-ticket.md +27 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/pr/merge.md +60 -9
- package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
- package/dist/agent-src/commands/roadmap/create.md +77 -29
- package/dist/agent-src/commands/roadmap/materialize.md +27 -0
- package/dist/agent-src/commands/roadmap/process-full.md +123 -18
- package/dist/agent-src/commands/team.md +22 -0
- package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +97 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
- package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
- package/dist/agent-src/rules/autonomous-execution.md +15 -8
- package/dist/agent-src/rules/delegation-policy.md +23 -45
- package/dist/agent-src/rules/design-fidelity.md +32 -37
- package/dist/agent-src/rules/evaluator-independence.md +43 -49
- package/dist/agent-src/rules/test-first.md +41 -0
- package/dist/agent-src/rules/user-interaction.md +31 -30
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
- package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
- package/dist/agent-src/skills/database/data/manifest.json +4 -4
- package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
- package/dist/agent-src/skills/design-review/SKILL.md +50 -0
- package/dist/agent-src/skills/error-handling-patterns/SKILL.md +9 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
- package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +188 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/dist/cli-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
- package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
- package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
- package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
- package/dist/cli-delegate/chunk-NUHLI2RX.js +80 -0
- package/dist/cli-delegate/{chunk-HZ52BRGG.js → chunk-P6ZHP5W2.js} +15 -90
- package/dist/cli-delegate/{chunk-TYSY33MD.js → chunk-QJWAUMJW.js} +12 -2
- package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
- package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
- package/dist/cli-delegate/{chunk-2KKXJW25.js → chunk-S3VFXMTM.js} +1 -1
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
- package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
- package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +918 -325
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +5 -3
- package/dist/cli-delegate/cmd_export.js +9 -7
- package/dist/cli-delegate/cmd_fleet.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +6 -6
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +3 -2
- package/dist/cli-delegate/cmd_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +4 -4
- package/dist/cli-delegate/cmd_settings_get.js +28 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
- package/dist/cli-delegate/cmd_settings_set.js +14 -1
- package/dist/cli-delegate/cmd_sync.js +10 -8
- package/dist/cli-delegate/cmd_uninstall.js +16 -13
- 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 +12 -10
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +87 -40
- 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 +2601 -1073
- package/dist/install/claudeRuleRewrite.js +14 -3
- package/dist/install/conflict.js +60 -10
- package/dist/install/conflictTracker.js +218 -0
- package/dist/install/fsPrimitives.js +54 -0
- package/dist/install/install.mjs +2008 -1481
- package/dist/install/manifestFiles.js +259 -0
- package/dist/install/preflight.js +14 -2
- package/dist/install/preserve.js +157 -0
- package/dist/install/recordedOwnership.js +132 -0
- package/dist/install/txlog.js +11 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1612 -667
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +3 -2
- package/dist/scripts/_lib/fs_atomic.js +167 -0
- package/dist/scripts/_lib/installed_lock.js +1 -1
- package/dist/scripts/_lib/installed_tools.js +460 -0
- package/dist/scripts/_lib/lean_projection_mode.js +3 -2
- package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
- package/dist/server/io/yamlIO.js +50 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +17 -5
- package/dist/shared/missionExecution.js +97 -0
- package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +26 -7
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/docs/guidelines/design-fidelity-routing.md +29 -0
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/src/config/agent-settings.template.yml +139 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +18 -5
- package/src/config/continuity-surface.json +28 -0
- package/src/config/gate-coverage.yml +146 -13
- package/src/config/gate-violation-baselines.json +6 -6
- package/src/config/hook-token-budget.json +2 -0
- package/src/config/host-injection-effect.json +1 -1
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/preamble-payload-budget.json +12 -7
- package/src/config/preamble-payload-exceptions.json +28 -0
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/install/claudeRuleRewrite.ts +16 -2
- package/src/install/conflict.ts +65 -9
- package/src/install/conflictTracker.ts +299 -0
- package/src/install/fsPrimitives.ts +54 -0
- package/src/install/manifestFiles.ts +312 -0
- package/src/install/preflight.ts +16 -2
- package/src/install/preserve.ts +205 -0
- package/src/install/recordedOwnership.ts +149 -0
- package/src/install/txlog.ts +11 -2
- package/src/install/types.ts +19 -4
- package/src/scripts/_cli/cmd_conformance.ts +88 -15
- package/src/scripts/_cli/cmd_doctor.ts +4 -3
- package/src/scripts/_cli/cmd_route_explain.ts +9 -2
- package/src/scripts/_cli/cmd_settings_get.ts +44 -0
- package/src/scripts/_cli/doctor_execution.ts +90 -0
- package/src/scripts/_dispatch.bash +3 -1
- package/src/scripts/_lib/activation_payload.ts +247 -0
- package/src/scripts/_lib/agent_settings.ts +4 -2
- package/src/scripts/_lib/authority_path.ts +102 -0
- package/src/scripts/_lib/base_ref_payload.ts +315 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- package/src/scripts/_lib/continuation_ladder.ts +149 -2
- package/src/scripts/_lib/council_transport.ts +129 -0
- package/src/scripts/_lib/delivery_ready.ts +112 -0
- package/src/scripts/_lib/dropped_decision.ts +142 -0
- package/src/scripts/_lib/forge_protection.ts +267 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/installed_lock.ts +1 -1
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/measured_payload_ceiling.ts +424 -0
- package/src/scripts/_lib/mission_record.ts +190 -0
- package/src/scripts/_lib/obligation_frequency.ts +217 -0
- package/src/scripts/_lib/obligations.ts +402 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/payload_catalogue_completeness.ts +211 -0
- package/src/scripts/_lib/release_findings_ingest.ts +324 -0
- package/src/scripts/_lib/release_highlights.ts +199 -10
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/release_tag_dispatch.ts +63 -0
- package/src/scripts/_lib/review_baseline.ts +254 -0
- package/src/scripts/_lib/review_skipped_record.ts +61 -1
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/standing_bound_ratchet.ts +216 -103
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/turn_end_refusals.ts +51 -4
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -0
- package/src/scripts/ai_council/config.ts +8 -11
- package/src/scripts/ai_council/fallback_config.ts +5 -3
- package/src/scripts/ai_council/ownership_classes.ts +118 -0
- package/src/scripts/ask_block_census.ts +285 -15
- package/src/scripts/check_beta_review_markers.ts +35 -4
- package/src/scripts/check_enforcement_matrix.ts +624 -0
- package/src/scripts/check_finding_dispositions.ts +167 -16
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +954 -0
- package/src/scripts/check_held_object_arrivals.ts +381 -0
- package/src/scripts/check_preamble_payload_budget.ts +474 -124
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +567 -0
- package/src/scripts/check_release_published.ts +1 -1
- package/src/scripts/check_reply_consistency.ts +86 -17
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/closure_scan.ts +333 -0
- package/src/scripts/compile_hook_manifest.ts +43 -3
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +127 -12
- package/src/scripts/hooks/block_kernel_rule_writes.ts +12 -2
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/design_pass_hook.ts +128 -3
- package/src/scripts/hooks/end_review_nudge_hook.ts +101 -21
- package/src/scripts/hooks/evidence_independence.ts +96 -0
- package/src/scripts/hooks/memory_recall_hook.ts +540 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
- package/src/scripts/hooks/review_baseline_hook.ts +130 -0
- package/src/scripts/hooks/rule_inject_hook.ts +49 -1
- package/src/scripts/hooks/run_continuation_hook.ts +17 -20
- package/src/scripts/hooks/turn_end_gate_hook.ts +232 -14
- package/src/scripts/hooks_doctor.ts +89 -1
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +107 -207
- package/src/scripts/interruption_report.ts +8 -2
- package/src/scripts/janitor.ts +15 -1
- package/src/scripts/lint_decision_classes.ts +464 -0
- package/src/scripts/lint_hook_manifest.ts +11 -0
- package/src/scripts/lint_major_migration_sections.ts +450 -0
- package/src/scripts/lint_plan_risk_register.ts +47 -3
- package/src/scripts/lint_roadmap_blockers.ts +61 -0
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/measure_turn_end_gate.ts +302 -5
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/release.ts +66 -162
- package/src/scripts/release_drill.ts +460 -15
- package/src/scripts/release_env.ts +1 -1
- package/src/scripts/release_publication.ts +52 -1
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_held_object_arrivals.ts +456 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +4 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/sync_agent_settings.ts +140 -18
- package/src/scripts/trigger_eval_grandfather.json +0 -1
- package/src/scripts/ui_conformance_probe.ts +622 -0
- package/src/server/io/yamlIO.ts +48 -0
- package/src/server/schemas/settings.ts +31 -7
- package/src/shared/missionExecution.ts +126 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* A held object cited as blocking live work carries an arrival count.
|
|
4
|
+
*
|
|
5
|
+
* WHAT A HELD OBJECT IS, AND WHAT MAKES ONE VISIBLE
|
|
6
|
+
* -------------------------------------------------
|
|
7
|
+
* A stub or a parked roadmap is an argument the estate decided not to run
|
|
8
|
+
* right now. Every one of them was created by a round that raised its subject.
|
|
9
|
+
* That first round is the arrival the object already records by existing, so a
|
|
10
|
+
* held object nobody has raised again owes no counter — there is nothing to
|
|
11
|
+
* count.
|
|
12
|
+
*
|
|
13
|
+
* What changes that is a CITATION: a live roadmap naming the object inside a
|
|
14
|
+
* blocker, which is the estate saying "this held thing is in the way of work
|
|
15
|
+
* we are doing now". That is the subject arriving a second time, and from then
|
|
16
|
+
* on the number is the fact a reader needs. Without it the next round meets a
|
|
17
|
+
* fresh argument instead of a count, re-derives the same conclusion, and the
|
|
18
|
+
* recurrence stays invisible — the failure this check exists to notice.
|
|
19
|
+
*
|
|
20
|
+
* WHY THE CITER SET IS THE LIVE ESTATE AND NOTHING ELSE
|
|
21
|
+
* -----------------------------------------------------
|
|
22
|
+
* Measured before the rule was written, not after. Widening the citers to
|
|
23
|
+
* every markdown file in the tree fires on 56 of 72 held objects, and widening
|
|
24
|
+
* only to archived roadmaps and stored review inputs fires on 54 of 66. Both
|
|
25
|
+
* populations are dominated by records that are historical by construction: an
|
|
26
|
+
* archived roadmap asserts what was true when it closed, and a stored review
|
|
27
|
+
* input asserts what a reviewer saw on a scope that no longer exists. Neither
|
|
28
|
+
* is the estate asking again. A check reading them as live citations reds on
|
|
29
|
+
* four fifths of its corpus on the day it lands, which is a suppression target
|
|
30
|
+
* wearing a control's clothes.
|
|
31
|
+
*
|
|
32
|
+
* So a citer is an active or parked roadmap, and the citation must sit in that
|
|
33
|
+
* roadmap's blocker scope rather than anywhere in its prose. Both narrowings
|
|
34
|
+
* are load-bearing and both were measured.
|
|
35
|
+
*
|
|
36
|
+
* ADVISORY BY DEFAULT, AND THE REASON IS RECORDED
|
|
37
|
+
* -----------------------------------------------
|
|
38
|
+
* It ships reporting rather than blocking. The false-positive rate over the
|
|
39
|
+
* live corpus was measured first and is 0 of 4 firings, which would permit
|
|
40
|
+
* blocking on its own. What forbids it today is that the same reading has the
|
|
41
|
+
* check red on four live objects the moment it lands, and a gate that arrives
|
|
42
|
+
* red is a backlog rather than a control. `--enforce` exits non-zero, so the
|
|
43
|
+
* promotion is a flag rather than an edit, and the condition is written into
|
|
44
|
+
* the measurement record beside the rate.
|
|
45
|
+
*
|
|
46
|
+
* Exit: 0 advisory (always) · with `--enforce`, 1 on any firing · 2 on a
|
|
47
|
+
* usage error or a dead scope.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
import * as fs from 'node:fs';
|
|
51
|
+
import * as os from 'node:os';
|
|
52
|
+
import * as path from 'node:path';
|
|
53
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
54
|
+
|
|
55
|
+
import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
|
|
56
|
+
import { reportScanned } from './_lib/scan_scope.js';
|
|
57
|
+
|
|
58
|
+
const _HERE = path.resolve(fileURLToPath(import.meta.url));
|
|
59
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
60
|
+
|
|
61
|
+
/** Directories whose members are held objects. */
|
|
62
|
+
export const HELD_DIRS = ['agents/roadmaps/stubs', 'agents/roadmaps/later'] as const;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Directories whose roadmaps are live citers.
|
|
66
|
+
*
|
|
67
|
+
* The active tree, plus the parked tree because a parked roadmap resumes and
|
|
68
|
+
* its blockers resume with it. The archived tree is deliberately absent.
|
|
69
|
+
*/
|
|
70
|
+
export const CITER_DIRS = ['agents/roadmaps', 'agents/roadmaps/later'] as const;
|
|
71
|
+
|
|
72
|
+
/** The line a held object carries once its subject has arrived again. */
|
|
73
|
+
export const ARRIVAL_RE = /^>\s*\*\*Arrivals:\*\*\s*\S/m;
|
|
74
|
+
|
|
75
|
+
/** A citation of a held object, by file name, inside a citer's prose. */
|
|
76
|
+
const HELD_REF_RE = /(?:stubs|later)\/([a-z0-9][a-z0-9\-.]*\.md)/g;
|
|
77
|
+
|
|
78
|
+
export interface Finding {
|
|
79
|
+
/** Repo-relative path of the held object with no counter. */
|
|
80
|
+
held: string;
|
|
81
|
+
/** Repo-relative paths of the live citers naming it inside a blocker. */
|
|
82
|
+
citers: string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface Result {
|
|
86
|
+
findings: Finding[];
|
|
87
|
+
/** Held objects cited inside a live blocker — the population the rate is taken over. */
|
|
88
|
+
cited: number;
|
|
89
|
+
/** Held objects found at all, so a collapsed corpus is visible. */
|
|
90
|
+
held: number;
|
|
91
|
+
/**
|
|
92
|
+
* Citer roots that EXIST and could not be read. Neither an empty root nor
|
|
93
|
+
* an absent one qualifies — both are real estate states. A name here means
|
|
94
|
+
* the scan could not see where the citations live, and that must never
|
|
95
|
+
* resolve to "nothing is cited".
|
|
96
|
+
*/
|
|
97
|
+
deadCiterRoots: string[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* List the markdown directly under `dir`.
|
|
102
|
+
*
|
|
103
|
+
* `readable` separates the THREE states an empty list can mean, not two.
|
|
104
|
+
*
|
|
105
|
+
* A directory that exists and holds no markdown is a real estate state. So is a
|
|
106
|
+
* directory that does not exist at all: git cannot track an empty directory, so
|
|
107
|
+
* "nothing is parked" IS an absent `later/` in a fresh clone. Both are
|
|
108
|
+
* `readable` — there is nothing to read and that is the answer.
|
|
109
|
+
*
|
|
110
|
+
* A directory that exists and cannot be read is the failed measurement, and the
|
|
111
|
+
* only one that may block. An earlier form of this function collapsed all three
|
|
112
|
+
* into `[]`, which let a moved citer root report an affirmative clean verdict;
|
|
113
|
+
* the first repair then over-corrected and folded ABSENT in with unreadable,
|
|
114
|
+
* turning a fresh clone with nothing parked into a hard exit 2.
|
|
115
|
+
*/
|
|
116
|
+
function listMarkdown(dir: string): { files: string[]; readable: boolean } {
|
|
117
|
+
let entries: fs.Dirent[];
|
|
118
|
+
try {
|
|
119
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
120
|
+
} catch (e) {
|
|
121
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
122
|
+
// ENOENT/ENOTDIR: nothing is there to read, which is a real state.
|
|
123
|
+
// Anything else (EACCES, EMFILE, EIO): it is there and we failed.
|
|
124
|
+
return { files: [], readable: code === 'ENOENT' || code === 'ENOTDIR' };
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
files: entries
|
|
128
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
129
|
+
.map((e) => path.join(dir, e.name))
|
|
130
|
+
.sort(),
|
|
131
|
+
readable: true,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function read(file: string): string {
|
|
136
|
+
try {
|
|
137
|
+
return fs.readFileSync(file, 'utf8');
|
|
138
|
+
} catch {
|
|
139
|
+
return '';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Held-object names a citer raises inside its blocker scope.
|
|
145
|
+
*
|
|
146
|
+
* Blocker scope opens at a `## Blockers` heading or at any `### blocker:`
|
|
147
|
+
* heading and closes at the next `## ` heading. A mention in a phase step or a
|
|
148
|
+
* risk row is the estate referring to the object, not declaring itself blocked
|
|
149
|
+
* on it, and the two must not read the same.
|
|
150
|
+
*/
|
|
151
|
+
export function blockerCitations(text: string): Set<string> {
|
|
152
|
+
const out = new Set<string>();
|
|
153
|
+
let inBlocker = false;
|
|
154
|
+
let inFence = false;
|
|
155
|
+
for (const line of text.split('\n')) {
|
|
156
|
+
// A fenced block is illustration, not structure. Without this the
|
|
157
|
+
// scanner was fence-blind: a roadmap quoting the blocker shape inside
|
|
158
|
+
// a code fence opened blocker scope for the rest of its section, and
|
|
159
|
+
// every held-object mention there counted as a live citation — the
|
|
160
|
+
// exact discrimination this function exists to make, inverted.
|
|
161
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
162
|
+
inFence = !inFence;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (inFence) continue;
|
|
166
|
+
if (line.startsWith('## ')) inBlocker = line.trim().toLowerCase().startsWith('## blockers');
|
|
167
|
+
if (line.startsWith('### blocker:')) inBlocker = true;
|
|
168
|
+
if (!inBlocker) continue;
|
|
169
|
+
HELD_REF_RE.lastIndex = 0;
|
|
170
|
+
let m: RegExpExecArray | null;
|
|
171
|
+
while ((m = HELD_REF_RE.exec(line)) !== null) {
|
|
172
|
+
const name = m[1];
|
|
173
|
+
if (name !== undefined) out.add(name);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function scan(root: string): Result {
|
|
180
|
+
const held = new Map<string, string>();
|
|
181
|
+
for (const d of HELD_DIRS) {
|
|
182
|
+
for (const f of listMarkdown(path.join(root, d)).files) held.set(path.basename(f), f);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const citerFiles = new Set<string>();
|
|
186
|
+
const deadCiterRoots: string[] = [];
|
|
187
|
+
for (const d of CITER_DIRS) {
|
|
188
|
+
const found = listMarkdown(path.join(root, d));
|
|
189
|
+
if (!found.readable) deadCiterRoots.push(d);
|
|
190
|
+
for (const f of found.files) citerFiles.add(f);
|
|
191
|
+
}
|
|
192
|
+
// Parked roadmaps are citers AND held objects, so the two sets overlap by
|
|
193
|
+
// construction. Only SELF-citation is excluded — a file naming itself. A
|
|
194
|
+
// held object citing a DIFFERENT held object still counts, which is
|
|
195
|
+
// deliberate and is what the live corpus is made of: all four current
|
|
196
|
+
// firings have a parked roadmap as the citer. An earlier version of this
|
|
197
|
+
// comment described a held-cites-held exclusion the code has never
|
|
198
|
+
// implemented, which would have read the whole firing population as
|
|
199
|
+
// excluded when auditing the adjudicated rate.
|
|
200
|
+
const citations = new Map<string, Set<string>>();
|
|
201
|
+
for (const f of [...citerFiles].sort()) {
|
|
202
|
+
const self = path.basename(f);
|
|
203
|
+
for (const name of blockerCitations(read(f))) {
|
|
204
|
+
if (name === self) continue;
|
|
205
|
+
if (!held.has(name)) continue;
|
|
206
|
+
const set = citations.get(name) ?? new Set<string>();
|
|
207
|
+
set.add(path.relative(root, f));
|
|
208
|
+
citations.set(name, set);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const findings: Finding[] = [];
|
|
213
|
+
for (const [name, citers] of [...citations.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
214
|
+
const file = held.get(name);
|
|
215
|
+
if (file === undefined) continue;
|
|
216
|
+
if (ARRIVAL_RE.test(read(file))) continue;
|
|
217
|
+
findings.push({ held: path.relative(root, file), citers: [...citers].sort() });
|
|
218
|
+
}
|
|
219
|
+
return { findings, cited: citations.size, held: held.size, deadCiterRoots };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function check(root: string, enforce: boolean, write = process.stdout.write.bind(process.stdout)): number {
|
|
223
|
+
const res = scan(root);
|
|
224
|
+
// Published before the verdict on every path: a gate that says what it read
|
|
225
|
+
// only when it passes leaves the coverage census blind exactly when it
|
|
226
|
+
// matters.
|
|
227
|
+
reportScanned({
|
|
228
|
+
gate: 'check_held_object_arrivals',
|
|
229
|
+
scanned: res.cited,
|
|
230
|
+
units: 'held object(s) cited inside a live blocker',
|
|
231
|
+
roots: [...CITER_DIRS],
|
|
232
|
+
allowEmpty:
|
|
233
|
+
'EMPTY_VALID: zero held objects cited inside a live blocker is a real and ' +
|
|
234
|
+
'desirable estate state — nothing is currently blocked on a held argument. Both ' +
|
|
235
|
+
'the held corpus and every citer root are asserted separately below, so an empty ' +
|
|
236
|
+
'reading can only mean the estate, never an unread directory.',
|
|
237
|
+
});
|
|
238
|
+
if (res.held === 0) {
|
|
239
|
+
write('❌ no held objects found — the held-object directories are empty or moved.\n');
|
|
240
|
+
return 2;
|
|
241
|
+
}
|
|
242
|
+
// A citer root that EXISTS and cannot be read is a scan that could not see
|
|
243
|
+
// where the citations live. Reporting that as "nothing is cited" is the
|
|
244
|
+
// silent-green this gate exists to avoid, so it is a hard 2 on every path
|
|
245
|
+
// including advisory — the `res.held === 0` guard above does not reach it,
|
|
246
|
+
// because the held corpus stays readable when only a citer root breaks.
|
|
247
|
+
// An ABSENT root is not this case and does not reach here: `listMarkdown`
|
|
248
|
+
// reports ENOENT as readable, because git cannot track an empty directory
|
|
249
|
+
// and a fresh clone with nothing parked has no `later/` at all.
|
|
250
|
+
if (res.deadCiterRoots.length > 0) {
|
|
251
|
+
write(
|
|
252
|
+
`❌ citer root(s) present but unreadable: ${res.deadCiterRoots.join(', ')}. ` +
|
|
253
|
+
'This is a failed measurement, not a clean estate.\n',
|
|
254
|
+
);
|
|
255
|
+
return 2;
|
|
256
|
+
}
|
|
257
|
+
if (res.findings.length === 0) {
|
|
258
|
+
write(
|
|
259
|
+
`✅ ${String(res.cited)} held object(s) cited inside a live blocker, all carrying an arrival line ` +
|
|
260
|
+
`(of ${String(res.held)} held objects).\n`,
|
|
261
|
+
);
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
const label = enforce ? '❌' : '⚠️';
|
|
265
|
+
write(
|
|
266
|
+
`${label} ${String(res.findings.length)} of ${String(res.cited)} held object(s) cited inside a live blocker ` +
|
|
267
|
+
'carry no arrival line:\n',
|
|
268
|
+
);
|
|
269
|
+
for (const f of res.findings) {
|
|
270
|
+
write(` · ${f.held}\n`);
|
|
271
|
+
for (const c of f.citers) write(` cited by ${c}\n`);
|
|
272
|
+
}
|
|
273
|
+
write(
|
|
274
|
+
'\nEach one is a subject that arrived at least twice — once to create the object, ' +
|
|
275
|
+
'once in the blocker above. Derive the count with `report_held_object_arrivals ' +
|
|
276
|
+
'<object> --pattern <subject>` and write the line by hand, with its pattern and ' +
|
|
277
|
+
'denominator, so the number carries a reason.\n',
|
|
278
|
+
);
|
|
279
|
+
if (!enforce) {
|
|
280
|
+
write('\nAdvisory: exiting 0. `--enforce` turns these into a failure.\n');
|
|
281
|
+
}
|
|
282
|
+
return enforce ? 1 : 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* The check proving it still discriminates.
|
|
287
|
+
*
|
|
288
|
+
* The accept cases are the two that a firing-on-everything version would get
|
|
289
|
+
* wrong: an uncited held object with no counter is a first arrival and owes
|
|
290
|
+
* nothing, and an object cited outside blocker scope is being referred to
|
|
291
|
+
* rather than blocked on.
|
|
292
|
+
*/
|
|
293
|
+
function selfTest(): number {
|
|
294
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'held-arrivals-selftest-'));
|
|
295
|
+
const HELD_BODY = '---\ncomplexity: lightweight\n---\n\n# Stub: a held subject\n\nbody\n';
|
|
296
|
+
const COUNTED_BODY =
|
|
297
|
+
'---\ncomplexity: lightweight\n---\n\n# Stub: a held subject\n\n' +
|
|
298
|
+
'> **Arrivals:** 3 — latest `round-b` (2026-01-02); earlier `round-a`.\n\nbody\n';
|
|
299
|
+
|
|
300
|
+
const build = (name: string, heldBody: string, citerBody: string): string => {
|
|
301
|
+
const root = path.join(tmp, name);
|
|
302
|
+
fs.mkdirSync(path.join(root, 'agents', 'roadmaps', 'stubs'), { recursive: true });
|
|
303
|
+
fs.mkdirSync(path.join(root, 'agents', 'roadmaps', 'later'), { recursive: true });
|
|
304
|
+
fs.writeFileSync(path.join(root, 'agents', 'roadmaps', 'stubs', 'road-to-held.md'), heldBody);
|
|
305
|
+
fs.writeFileSync(path.join(root, 'agents', 'roadmaps', 'road-to-live.md'), citerBody);
|
|
306
|
+
return root;
|
|
307
|
+
};
|
|
308
|
+
const citerWithBlocker =
|
|
309
|
+
'# Road to live work\n\n## Phase 1\n\n- [ ] 1.1 do a thing\n\n## Blockers\n\n' +
|
|
310
|
+
'### blocker: the held thing\n- **Status:** open\n- **Blocks:** everything\n' +
|
|
311
|
+
'- **What to do:** read `stubs/road-to-held.md` and decide.\n';
|
|
312
|
+
const citerWithoutBlocker =
|
|
313
|
+
'# Road to live work\n\n## Phase 1\n\n' +
|
|
314
|
+
'- [ ] 1.1 read `stubs/road-to-held.md` for background, then do a thing\n';
|
|
315
|
+
|
|
316
|
+
const run = (root: string, enforce: boolean): number =>
|
|
317
|
+
runGateCli(
|
|
318
|
+
REPO_ROOT,
|
|
319
|
+
'src/scripts/check_held_object_arrivals.ts',
|
|
320
|
+
enforce ? ['--root', root, '--enforce'] : ['--root', root],
|
|
321
|
+
root,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
return runSelfTest({
|
|
326
|
+
gate: 'check_held_object_arrivals',
|
|
327
|
+
minCases: 5,
|
|
328
|
+
minRejectCases: 2,
|
|
329
|
+
cases: [
|
|
330
|
+
{
|
|
331
|
+
name: 'a held object cited inside a blocker with no arrival line is rejected',
|
|
332
|
+
expect: 'reject',
|
|
333
|
+
run: () => run(build('cited-uncounted', HELD_BODY, citerWithBlocker), true),
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: 'an empty held corpus is rejected rather than reported clean',
|
|
337
|
+
expect: 'reject',
|
|
338
|
+
run: () => {
|
|
339
|
+
const root = path.join(tmp, 'dead-scope');
|
|
340
|
+
fs.mkdirSync(path.join(root, 'agents', 'roadmaps'), { recursive: true });
|
|
341
|
+
return run(root, false);
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: 'a first-arrival held object with no counter and no citation passes',
|
|
346
|
+
expect: 'accept',
|
|
347
|
+
run: () => run(build('uncited', HELD_BODY, '# Road to live work\n\n## Phase 1\n\n- [ ] 1.1 x\n'), true),
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: 'a held object mentioned outside blocker scope passes',
|
|
351
|
+
expect: 'accept',
|
|
352
|
+
run: () => run(build('outside-blocker', HELD_BODY, citerWithoutBlocker), true),
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: 'a cited held object that carries its arrival line passes',
|
|
356
|
+
expect: 'accept',
|
|
357
|
+
run: () => run(build('counted', COUNTED_BODY, citerWithBlocker), true),
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
});
|
|
361
|
+
} finally {
|
|
362
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function main(argv: readonly string[]): number {
|
|
367
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
368
|
+
process.stdout.write(
|
|
369
|
+
'usage: check_held_object_arrivals [--root DIR] [--enforce] [--self-test]\n\n' +
|
|
370
|
+
' --enforce exit 1 on any firing; the default is advisory\n',
|
|
371
|
+
);
|
|
372
|
+
return 0;
|
|
373
|
+
}
|
|
374
|
+
if (argv.includes('--self-test')) return selfTest();
|
|
375
|
+
const i = argv.indexOf('--root');
|
|
376
|
+
return check(i === -1 ? REPO_ROOT : (argv[i + 1] ?? REPO_ROOT), argv.includes('--enforce'));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
380
|
+
process.exit(main(process.argv.slice(2)));
|
|
381
|
+
}
|