@event4u/agent-config 14.6.0 → 14.8.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 +255 -145
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +264 -10
- package/dist/agent-src/commands/analyze.md +1 -1
- package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
- package/dist/agent-src/commands/evals/roadmap.json +17 -1
- package/dist/agent-src/commands/pr/create.md +14 -2
- package/dist/agent-src/commands/pr/merge.md +379 -0
- package/dist/agent-src/commands/roadmap/process-full.md +272 -32
- package/dist/agent-src/commands/roadmap.md +1 -1
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +95 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +289 -106
- package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
- package/dist/agent-src/rules/context-hygiene.md +2 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +67 -32
- 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 +20 -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 +26 -7
- package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
- 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 +296 -53
- package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
- 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-record/SKILL.md +61 -3
- package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -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/security/SKILL.md +8 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
- 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 +65 -4
- 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 +2 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
- package/dist/cli-delegate/{chunk-P6LQDIWK.js → chunk-7PRE6ZV2.js} +338 -0
- package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
- package/dist/cli-delegate/chunk-E4XIZDIB.js +402 -0
- package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-EEJUMCDZ.js} +16 -16
- package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
- package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-H4VIVD4C.js} +385 -284
- package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
- package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-LPPWO5CE.js} +12 -8
- package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
- package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-N7CDDGI3.js} +4 -23
- package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-PMEKGDED.js} +74 -167
- 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_adr_effective.js +1162 -0
- package/dist/cli-delegate/cmd_conformance.js +466 -716
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +10 -10
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +10 -9
- package/dist/cli-delegate/cmd_handoff.js +10 -9
- package/dist/cli-delegate/cmd_memory_get.js +2 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +5 -5
- package/dist/cli-delegate/cmd_prune.js +11 -10
- package/dist/cli-delegate/cmd_route_audit.js +6 -5
- package/dist/cli-delegate/cmd_route_explain.js +3 -3
- package/dist/cli-delegate/cmd_session_recycle.js +840 -30
- package/dist/cli-delegate/cmd_settings_get.js +3 -3
- package/dist/cli-delegate/cmd_settings_migrate.js +10 -9
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +11 -10
- package/dist/cli-delegate/cmd_uninstall.js +13 -11
- package/dist/cli-delegate/cmd_update.js +4 -4
- package/dist/cli-delegate/cmd_upgrade.js +4 -4
- package/dist/cli-delegate/cmd_validate.js +11 -10
- package/dist/cli-delegate/cmd_versions.js +3 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +111 -37
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +5 -5
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +15 -12
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +11 -8
- package/dist/hooks/dispatch.js +3165 -2192
- package/dist/install/claudePathsPlan.js +187 -0
- package/dist/install/claudePathsPlan.js.map +1 -0
- package/dist/install/globalRuleLayers.js +169 -0
- package/dist/install/globalRuleLayers.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 +432 -0
- package/dist/install/partitionEligibility.js.map +1 -0
- package/dist/install/ruleLayerPartition.js +157 -0
- package/dist/install/ruleLayerPartition.js.map +1 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1863 -1234
- 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/_lib/prune_empty_dirs.js +85 -0
- package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
- package/dist/scripts/condense.js +63 -251
- 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/artifact-drafting-protocol-mechanics.md +45 -1
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
- package/docs/guidelines/agent-infra/false-green.md +47 -1
- 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 +26 -7
- package/src/agent-src/scripts/dashboard_mode.ts +212 -0
- 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 +296 -53
- 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 +39 -0
- package/src/config/estate-count-budget.json +612 -3
- package/src/config/evaluator-budgets.json +3 -3
- package/src/config/gate-coverage.yml +220 -12
- package/src/config/gate-violation-baselines.json +73 -63
- package/src/config/gitignore-block.txt +15 -2
- package/src/config/hook-latency-budget.json +5 -0
- package/src/config/rule-activation-census.json +14 -6
- package/src/config/rule-stub-ceilings.json +34 -7
- package/src/install/claudePathsPlan.ts +196 -0
- package/src/install/globalRuleLayers.ts +197 -0
- package/src/install/hostLayerFingerprint.ts +145 -0
- package/src/install/partitionEligibility.ts +502 -0
- package/src/install/ruleLayerPartition.ts +212 -0
- package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
- package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
- package/src/scripts/_dispatch.bash +17 -1
- package/src/scripts/_lib/ac_heading.ts +30 -0
- package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
- package/src/scripts/_lib/adr_frontmatter.ts +459 -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 +49 -9
- package/src/scripts/_lib/installed_lock.ts +50 -5
- package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
- package/src/scripts/_lib/md_section.ts +129 -0
- package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
- package/src/scripts/_lib/scope_guard.sh +26 -3
- package/src/scripts/_lib/self_repair.ts +19 -0
- package/src/scripts/_lib/self_repair_class_b.ts +171 -0
- package/src/scripts/adr/evidence_census.ts +899 -0
- package/src/scripts/adr/regenerate_index.ts +12 -32
- package/src/scripts/adr_cite_check.ts +540 -39
- package/src/scripts/ai_council/cli_help.ts +55 -0
- package/src/scripts/audit_adr_coverage.ts +103 -17
- package/src/scripts/before_complete_hook.ts +153 -28
- package/src/scripts/bench_ab_v2_complexity.ts +27 -7
- package/src/scripts/bench_hook_latency.ts +204 -0
- package/src/scripts/build_archive_index.ts +34 -0
- package/src/scripts/check_adr_frontmatter.ts +574 -39
- package/src/scripts/check_hook_bundle_content.ts +219 -0
- package/src/scripts/check_new_adr_evidence.ts +529 -0
- package/src/scripts/check_references.ts +78 -0
- package/src/scripts/check_release_published.ts +32 -11
- package/src/scripts/check_rule_layer_partition.ts +531 -0
- package/src/scripts/check_single_delivery.ts +194 -17
- package/src/scripts/check_standing_rule_delivery.ts +8 -2
- package/src/scripts/ci_settle.ts +170 -0
- package/src/scripts/condense.ts +79 -259
- package/src/scripts/council_cli.ts +3 -3
- package/src/scripts/dispatch_r2_reviewer.ts +6 -14
- package/src/scripts/external_sources_denylist.json +4 -1
- 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 +21 -1
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/dispatch_hook.ts +154 -187
- package/src/scripts/hooks/dispatch_issues.ts +38 -19
- package/src/scripts/hooks/fallback_yaml.ts +66 -0
- package/src/scripts/hooks/host_semantics.ts +21 -0
- package/src/scripts/hooks/injection_budget.ts +13 -3
- package/src/scripts/hooks/py_json_dumps.ts +103 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
- package/src/scripts/hooks/run_continuation_hook.ts +8 -5
- 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 +870 -21
- package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
- 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/injection_scan_hook.ts +137 -5
- 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_hook_manifest.ts +49 -0
- package/src/scripts/lint_plan_risk_register.ts +4 -3
- package/src/scripts/lint_provenance_vocabulary.ts +472 -4
- package/src/scripts/lint_skill_router_head.ts +4 -4
- package/src/scripts/mcp_server/tools.ts +2 -2
- 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 +6 -0
- package/src/scripts/routing_doctor.ts +5 -1
- package/src/scripts/self_repair_hook.ts +37 -5
- package/src/scripts/session_register_hook.ts +11 -0
- 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 +170 -20
- 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-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
- package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
- package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-MMSGVZKV.js} +3 -3
package/src/scripts/condense.ts
CHANGED
|
@@ -251,7 +251,22 @@ function _stripChars(s: string, chars: string): string {
|
|
|
251
251
|
// module-level CLI self-invoke into its bundle. Re-exported here because four
|
|
252
252
|
// in-repo consumers import it from `condense.js`.
|
|
253
253
|
export { rule_in_scope } from '../install/ruleInScope.js';
|
|
254
|
+
// Re-exported for in-repo consumers; definitions moved beside emit_host_rules_cli.ts (Phase 5).
|
|
255
|
+
export {
|
|
256
|
+
CLAUDE_PATHS_PATTERN_BUDGET,
|
|
257
|
+
_claude_paths_plan,
|
|
258
|
+
_escape_claude_bracket,
|
|
259
|
+
_expanded_pattern_count,
|
|
260
|
+
_has_non_path_trigger,
|
|
261
|
+
_is_unresolved_placeholder,
|
|
262
|
+
derive_trigger_globs,
|
|
263
|
+
type ClaudePathsPlan,
|
|
264
|
+
} from '../install/claudePathsPlan.js';
|
|
254
265
|
import { rule_in_scope } from '../install/ruleInScope.js';
|
|
266
|
+
import { pruneEmptyDirs } from './_lib/prune_empty_dirs.js';
|
|
267
|
+
import { commandsWithheld, partitionActive, personaPartition, setPartitionAnnounce } from '../install/partitionEligibility.js'; // ADR-236
|
|
268
|
+
import { dedupableRules, partitionRulesForDir } from '../install/ruleLayerPartition.js'; // ADR-236, per-host evidence
|
|
269
|
+
import { _claude_paths_plan, derive_trigger_globs } from '../install/claudePathsPlan.js';
|
|
255
270
|
|
|
256
271
|
const _HERE = path.dirname(fileURLToPath(import.meta.url)); // <repo>/src/scripts
|
|
257
272
|
const _DEFAULT_PROJECT_ROOT = path.resolve(_HERE, '..', '..');
|
|
@@ -304,6 +319,7 @@ function _deriveState(root: string): ModuleState {
|
|
|
304
319
|
}
|
|
305
320
|
|
|
306
321
|
export const MODULE_STATE: ModuleState = _deriveState(_DEFAULT_PROJECT_ROOT);
|
|
322
|
+
setPartitionAnnounce(success); // ADR-236: the mode line must be visible at the default level
|
|
307
323
|
|
|
308
324
|
/**
|
|
309
325
|
* Test seam — reassign one or more module-state fields, mirroring the pytest
|
|
@@ -425,15 +441,6 @@ function _read_augment_rules_use_symlinks(): boolean {
|
|
|
425
441
|
return false;
|
|
426
442
|
}
|
|
427
443
|
|
|
428
|
-
/**
|
|
429
|
-
* Per-tool user-scope rule directory, for the scope-dedup below. Only tools
|
|
430
|
-
* whose host reads a user-scope rules directory can have a redundant twin;
|
|
431
|
-
* everything else has nothing to de-duplicate against.
|
|
432
|
-
*/
|
|
433
|
-
const USER_SCOPE_RULE_DIRS: Readonly<Record<string, string>> = {
|
|
434
|
-
'.claude/rules': path.join('.claude', 'rules'),
|
|
435
|
-
};
|
|
436
|
-
|
|
437
444
|
function _read_projection_scope_dedup(): boolean {
|
|
438
445
|
const data = load_agent_settings({ project_path: MODULE_STATE.SETTINGS_FILE });
|
|
439
446
|
const projection = data['projection'];
|
|
@@ -450,60 +457,6 @@ function _read_projection_scope_dedup(): boolean {
|
|
|
450
457
|
return false;
|
|
451
458
|
}
|
|
452
459
|
|
|
453
|
-
/**
|
|
454
|
-
* Rules whose user-scope twin is BYTE-IDENTICAL to the source, i.e. safe to
|
|
455
|
-
* skip at project scope: the host still loads the same text, once instead of
|
|
456
|
-
* twice, so nothing the model sees changes.
|
|
457
|
-
*
|
|
458
|
-
* Byte-identity is the whole safety argument and is not negotiable. Keying on
|
|
459
|
-
* the filename alone would silently let a stale globally-installed copy win
|
|
460
|
-
* whenever the two scopes hold different versions — which is the NORMAL state
|
|
461
|
-
* while developing the package (measured: 110/110 shared filenames differing in
|
|
462
|
-
* bytes). This is deliberately not the thin-projection mechanism: no rule
|
|
463
|
-
* becomes trigger-gated, so the quality floor that disabled thin projection
|
|
464
|
-
* does not apply here.
|
|
465
|
-
*/
|
|
466
|
-
function _dedupable_rules(tool_dir: string, rules: readonly string[], userHome: string): Set<string> {
|
|
467
|
-
const relative = USER_SCOPE_RULE_DIRS[tool_dir];
|
|
468
|
-
if (relative === undefined) {
|
|
469
|
-
return new Set();
|
|
470
|
-
}
|
|
471
|
-
// A hostile or simply absent $HOME must make the dedup inert, not
|
|
472
|
-
// adventurous. In a container `$HOME` is often unset (so `homedir()` can
|
|
473
|
-
// resolve to `/`) or world-writable, and this function decides which rules
|
|
474
|
-
// to STOP emitting — reading an unexpected tree there is how a projection
|
|
475
|
-
// silently loses a rule. Council review of PR #1055 raised exactly this.
|
|
476
|
-
let userDirStat: fs.Stats;
|
|
477
|
-
const userDir = path.join(userHome, relative);
|
|
478
|
-
try {
|
|
479
|
-
userDirStat = fs.statSync(userDir);
|
|
480
|
-
} catch {
|
|
481
|
-
return new Set();
|
|
482
|
-
}
|
|
483
|
-
if (!userDirStat.isDirectory()) {
|
|
484
|
-
return new Set();
|
|
485
|
-
}
|
|
486
|
-
// World-writable user scope: anyone on the box could plant a byte-identical
|
|
487
|
-
// twin and thereby delete a rule from the project projection. Refuse.
|
|
488
|
-
if ((userDirStat.mode & 0o002) !== 0) {
|
|
489
|
-
_print(` ⚠️ ${tool_dir}: user-scope rules dir is world-writable — scope-dedup skipped`);
|
|
490
|
-
return new Set();
|
|
491
|
-
}
|
|
492
|
-
const skip = new Set<string>();
|
|
493
|
-
for (const rule of rules) {
|
|
494
|
-
const twin = path.join(userDir, rule);
|
|
495
|
-
const source = path.join(MODULE_STATE.RULES_SOURCE, rule);
|
|
496
|
-
try {
|
|
497
|
-
if (!_isFile(twin) || !_isFile(source)) continue;
|
|
498
|
-
if (fs.readFileSync(twin).equals(fs.readFileSync(source))) {
|
|
499
|
-
skip.add(rule);
|
|
500
|
-
}
|
|
501
|
-
} catch {
|
|
502
|
-
// An unreadable twin is simply not de-duplicable — emit the project copy.
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
return skip;
|
|
506
|
-
}
|
|
507
460
|
|
|
508
461
|
function _lean_projection_mode(): string {
|
|
509
462
|
const data = load_agent_settings({ project_path: MODULE_STATE.SETTINGS_FILE });
|
|
@@ -1122,6 +1075,18 @@ function _scoped_rule_basenames(): string[] {
|
|
|
1122
1075
|
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
1123
1076
|
}
|
|
1124
1077
|
|
|
1078
|
+
/** Adapter over {@link partitionRulesForDir}; that module carries the reasoning. */
|
|
1079
|
+
export function partition_rules_for_dir(
|
|
1080
|
+
tool_dir: string,
|
|
1081
|
+
rules: readonly string[],
|
|
1082
|
+
user_home: string = process.env['HOME'] ?? os.homedir(),
|
|
1083
|
+
): string[] {
|
|
1084
|
+
return partitionRulesForDir({
|
|
1085
|
+
toolDir: tool_dir, rules, userHome: user_home, announce: _print,
|
|
1086
|
+
projectRoot: MODULE_STATE.PROJECT_ROOT, rulesSource: MODULE_STATE.RULES_SOURCE,
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1125
1090
|
/**
|
|
1126
1091
|
* The emit plan of {@link generate_rule_symlinks}: active tool dir → the exact
|
|
1127
1092
|
* rule basenames it projects there.
|
|
@@ -1146,8 +1111,12 @@ export function projected_rule_trees(
|
|
|
1146
1111
|
const dedup_on = _read_projection_scope_dedup();
|
|
1147
1112
|
const out: Record<string, string[]> = {};
|
|
1148
1113
|
for (const tool_dir of Object.keys(_filter_tool_dirs(TOOL_DIRS))) {
|
|
1149
|
-
const skip = dedup_on
|
|
1150
|
-
|
|
1114
|
+
const skip = dedup_on
|
|
1115
|
+
? dedupableRules({ toolDir: tool_dir, rules, userHome: user_home,
|
|
1116
|
+
rulesSource: MODULE_STATE.RULES_SOURCE, announce: _print })
|
|
1117
|
+
: new Set<string>();
|
|
1118
|
+
const kept = rules.filter((r) => !skip.has(r));
|
|
1119
|
+
out[tool_dir] = partition_rules_for_dir(tool_dir, kept, user_home);
|
|
1151
1120
|
}
|
|
1152
1121
|
return out;
|
|
1153
1122
|
}
|
|
@@ -1266,7 +1235,10 @@ export function generate_rule_symlinks(): number {
|
|
|
1266
1235
|
}
|
|
1267
1236
|
|
|
1268
1237
|
export function generate_windsurfrules(): number {
|
|
1269
|
-
|
|
1238
|
+
// Windsurf's LEGACY single-file surface — same host, so the same evidence, keyed
|
|
1239
|
+
// on `.windsurf/rules`. Without this line removing the per-run filter regressed
|
|
1240
|
+
// it 13 → 113: the per-run filter had been covering it by accident.
|
|
1241
|
+
const rules = partition_rules_for_dir('.windsurf/rules', _scoped_rule_basenames());
|
|
1270
1242
|
const parts = ['# Auto-generated from dist/agent-src/rules/ — do not edit directly\n'];
|
|
1271
1243
|
for (const rule of rules) {
|
|
1272
1244
|
const p = path.join(MODULE_STATE.RULES_SOURCE, rule);
|
|
@@ -1322,32 +1294,6 @@ function _yaml_scalar(value: string): string {
|
|
|
1322
1294
|
return JSON.stringify(value);
|
|
1323
1295
|
}
|
|
1324
1296
|
|
|
1325
|
-
/**
|
|
1326
|
-
* Derive host-native activation globs from a rule's `triggers:` frontmatter
|
|
1327
|
-
* (road-to-request-scoped-rule-load Phase 2). `file_pattern` triggers map
|
|
1328
|
-
* verbatim; `path_prefix` triggers map to `<prefix>**`. Keyword / phrase /
|
|
1329
|
-
* command triggers produce no glob — those rules stay
|
|
1330
|
-
* description-activated (Cursor Agent-Requested / Windsurf model_decision).
|
|
1331
|
-
*/
|
|
1332
|
-
export function derive_trigger_globs(meta: Record<string, unknown>): string[] {
|
|
1333
|
-
const triggers = meta['triggers'];
|
|
1334
|
-
if (!Array.isArray(triggers)) {
|
|
1335
|
-
return [];
|
|
1336
|
-
}
|
|
1337
|
-
const globs: string[] = [];
|
|
1338
|
-
for (const t of triggers) {
|
|
1339
|
-
if (t === null || typeof t !== 'object' || Array.isArray(t)) continue;
|
|
1340
|
-
const obj = t as Record<string, unknown>;
|
|
1341
|
-
if (typeof obj['file_pattern'] === 'string' && obj['file_pattern']) {
|
|
1342
|
-
globs.push(obj['file_pattern']);
|
|
1343
|
-
} else if (typeof obj['path_prefix'] === 'string' && obj['path_prefix']) {
|
|
1344
|
-
const prefix = obj['path_prefix'] as string;
|
|
1345
|
-
globs.push(prefix.endsWith('/') ? `${prefix}**` : `${prefix}**`);
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
// De-dup, keep declaration order (deterministic output).
|
|
1349
|
-
return [...new Set(globs)];
|
|
1350
|
-
}
|
|
1351
1297
|
|
|
1352
1298
|
export function _emit_cursor_mdc(source: string, target: string): void {
|
|
1353
1299
|
const [meta, body] = _parse_frontmatter(_readText(source));
|
|
@@ -1390,151 +1336,8 @@ export function _emit_windsurf_rule(source: string, target: string): void {
|
|
|
1390
1336
|
_writeText(target, lines.join('\n'));
|
|
1391
1337
|
}
|
|
1392
1338
|
|
|
1393
|
-
/**
|
|
1394
|
-
* A brace group the host expands, versus an agent-config placeholder that only
|
|
1395
|
-
* LOOKS like one.
|
|
1396
|
-
*
|
|
1397
|
-
* `path_prefix: "{module_root}/"` in `roadmap-ci-steps-policy` is resolved by
|
|
1398
|
-
* this suite from `modules.root_paths`. Emitted verbatim into a host `paths:`
|
|
1399
|
-
* list it is a brace group with no comma, which the host expands to the literal
|
|
1400
|
-
* string `{module_root}` — a pattern that matches no file. The rule would then
|
|
1401
|
-
* reach the model **never** instead of on-demand, and nothing would say so: the
|
|
1402
|
-
* probed contract records this exact degradation as silent
|
|
1403
|
-
* (`claude-code-rules-dir-contract.md` § constraints).
|
|
1404
|
-
*
|
|
1405
|
-
* Dropping the pattern is fail-safe in the one direction that matters. A rule
|
|
1406
|
-
* left with no pattern at all carries no `paths:` key and loads
|
|
1407
|
-
* unconditionally — the status quo, not a regression. A rule carrying a
|
|
1408
|
-
* no-op pattern is invisible.
|
|
1409
|
-
*
|
|
1410
|
-
* The discriminator is the comma, not a placeholder allowlist: `{ts,tsx}` is a
|
|
1411
|
-
* real alternation, `{module_root}` cannot be one.
|
|
1412
|
-
*/
|
|
1413
|
-
export function _is_unresolved_placeholder(pattern: string): boolean {
|
|
1414
|
-
for (const m of pattern.matchAll(/\{([^}]*)\}/g)) {
|
|
1415
|
-
if (!(m[1] as string).includes(',')) {
|
|
1416
|
-
return true;
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
return false;
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
/**
|
|
1423
|
-
* `[` opens a bracket expression for the host. An unparseable one makes that
|
|
1424
|
-
* pattern match nothing while the rule's other patterns keep working — so a
|
|
1425
|
-
* literal `[` in a trigger is escaped rather than trusted.
|
|
1426
|
-
*/
|
|
1427
|
-
export function _escape_claude_bracket(pattern: string): string {
|
|
1428
|
-
return pattern.replace(/\[/g, '\\[');
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
/**
|
|
1432
|
-
* Expanded-pattern cost of one glob: the product of its brace alternations.
|
|
1433
|
-
* The host budgets a rule's whole `paths:` list at 1,000 expanded patterns and
|
|
1434
|
-
* 4 MiB, and a list over budget is used UNEXPANDED — braces then match
|
|
1435
|
-
* literally, i.e. nothing. Counting keeps the emitter under the cliff instead
|
|
1436
|
-
* of discovering it as a silent no-op.
|
|
1437
|
-
*/
|
|
1438
|
-
export function _expanded_pattern_count(pattern: string): number {
|
|
1439
|
-
let n = 1;
|
|
1440
|
-
for (const m of pattern.matchAll(/\{([^}]*)\}/g)) {
|
|
1441
|
-
n *= (m[1] as string).split(',').length;
|
|
1442
|
-
}
|
|
1443
|
-
return n;
|
|
1444
|
-
}
|
|
1445
1339
|
|
|
1446
|
-
/** Host-documented budget for one rule's whole `paths:` list. */
|
|
1447
|
-
export const CLAUDE_PATHS_PATTERN_BUDGET = 1000;
|
|
1448
1340
|
|
|
1449
|
-
export interface ClaudePathsPlan {
|
|
1450
|
-
/** Patterns to emit under `paths:`, in declaration order. */
|
|
1451
|
-
globs: string[];
|
|
1452
|
-
/** Patterns deliberately not emitted, with the reason a reader can act on. */
|
|
1453
|
-
dropped: { pattern: string; reason: 'unresolved-placeholder' | 'over-budget' | 'mixed-triggers' }[];
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
/**
|
|
1457
|
-
* Does this rule declare any trigger that is NOT path-shaped?
|
|
1458
|
-
*
|
|
1459
|
-
* Load-bearing for `_claude_paths_plan` below, because one `triggers:` list
|
|
1460
|
-
* means two different things on two hosts. Cursor and Windsurf treat globs as
|
|
1461
|
-
* ADDITIVE — a rule with empty globs still reaches the agent through its
|
|
1462
|
-
* description (see `_emit_cursor_mdc`'s own comment). Claude Code treats
|
|
1463
|
-
* `paths:` as EXCLUSIVE: with it the rule loads on a path match; without it the
|
|
1464
|
-
* rule loads unconditionally. The emitter fed both semantics from one list, so
|
|
1465
|
-
* a rule that declared keywords *and* one path glob silently became
|
|
1466
|
-
* path-gated-only on Claude.
|
|
1467
|
-
*/
|
|
1468
|
-
export function _has_non_path_trigger(meta: Record<string, unknown>): boolean {
|
|
1469
|
-
const triggers = meta['triggers'];
|
|
1470
|
-
if (!Array.isArray(triggers)) return false;
|
|
1471
|
-
for (const t of triggers) {
|
|
1472
|
-
if (t === null || typeof t !== 'object' || Array.isArray(t)) continue;
|
|
1473
|
-
const obj = t as Record<string, unknown>;
|
|
1474
|
-
if (typeof obj['keyword'] === 'string' && obj['keyword']) return true;
|
|
1475
|
-
if (typeof obj['phrase'] === 'string' && obj['phrase']) return true;
|
|
1476
|
-
}
|
|
1477
|
-
return false;
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
/**
|
|
1481
|
-
* Derive the host's `paths:` list for one rule, with every drop recorded.
|
|
1482
|
-
*
|
|
1483
|
-
* Kernel / always-apply rules get an EMPTY list on purpose: absent `paths:` is
|
|
1484
|
-
* how this host says "load unconditionally", which is exactly what an Iron-Law
|
|
1485
|
-
* rule needs. Scoping one would be a correctness regression dressed as a byte
|
|
1486
|
-
* saving — and the same contract notes path-scoped rules are not re-injected
|
|
1487
|
-
* after `/compact`, so an obligation that must survive compaction cannot be
|
|
1488
|
-
* path-scoped at all.
|
|
1489
|
-
*/
|
|
1490
|
-
export function _claude_paths_plan(meta: Record<string, unknown>): ClaudePathsPlan {
|
|
1491
|
-
const always_apply = Boolean(meta['alwaysApply'] || meta['type'] === 'always');
|
|
1492
|
-
if (always_apply) {
|
|
1493
|
-
return { globs: [], dropped: [] };
|
|
1494
|
-
}
|
|
1495
|
-
const globs: string[] = [];
|
|
1496
|
-
const dropped: ClaudePathsPlan['dropped'] = [];
|
|
1497
|
-
|
|
1498
|
-
// A rule that ALSO declares keyword / phrase triggers gets no `paths:` at
|
|
1499
|
-
// all, and therefore keeps loading unconditionally on Claude Code.
|
|
1500
|
-
//
|
|
1501
|
-
// Emitting `paths:` here would narrow the rule to a path match and discard
|
|
1502
|
-
// every keyword the author wrote, because this host reads the list as the
|
|
1503
|
-
// whole gate rather than as one way in. Measured before this guard: 25 of
|
|
1504
|
-
// 110 projected rules carried `paths:` and 19 of them also carried keyword
|
|
1505
|
-
// triggers — `design-fidelity` lost 21 of them, in exactly the pasted-
|
|
1506
|
-
// screenshot and capability-URL handovers its own routing section names as
|
|
1507
|
-
// the classes it exists to catch. Nothing in the tree recorded a decision
|
|
1508
|
-
// to make that trade, which is why it is treated as a defect.
|
|
1509
|
-
//
|
|
1510
|
-
// The drop is REPORTED rather than silent, so a rule whose obligation
|
|
1511
|
-
// really is path-bound is visible as a candidate for removing its keyword
|
|
1512
|
-
// triggers — the narrowing then becomes an authoring decision instead of an
|
|
1513
|
-
// emitter side effect. Cursor and Windsurf are untouched: globs are
|
|
1514
|
-
// additive there, so the same rule keeps both routes.
|
|
1515
|
-
if (_has_non_path_trigger(meta)) {
|
|
1516
|
-
for (const raw of derive_trigger_globs(meta)) {
|
|
1517
|
-
dropped.push({ pattern: raw, reason: 'mixed-triggers' });
|
|
1518
|
-
}
|
|
1519
|
-
return { globs, dropped };
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
let spent = 0;
|
|
1523
|
-
for (const raw of derive_trigger_globs(meta)) {
|
|
1524
|
-
if (_is_unresolved_placeholder(raw)) {
|
|
1525
|
-
dropped.push({ pattern: raw, reason: 'unresolved-placeholder' });
|
|
1526
|
-
continue;
|
|
1527
|
-
}
|
|
1528
|
-
const cost = _expanded_pattern_count(raw);
|
|
1529
|
-
if (spent + cost > CLAUDE_PATHS_PATTERN_BUDGET) {
|
|
1530
|
-
dropped.push({ pattern: raw, reason: 'over-budget' });
|
|
1531
|
-
continue;
|
|
1532
|
-
}
|
|
1533
|
-
spent += cost;
|
|
1534
|
-
globs.push(_escape_claude_bracket(raw));
|
|
1535
|
-
}
|
|
1536
|
-
return { globs, dropped };
|
|
1537
|
-
}
|
|
1538
1341
|
|
|
1539
1342
|
/**
|
|
1540
1343
|
* Emit a Claude Code rule carrying the host's OWN activation key.
|
|
@@ -1605,12 +1408,16 @@ function _clean_modern_dir(target_dir: string, valid_names: ReadonlySet<string>)
|
|
|
1605
1408
|
export function generate_cursor_mdc_rules(): number {
|
|
1606
1409
|
const scope = _read_rule_workspaces();
|
|
1607
1410
|
const pack_scope = _read_rule_packs();
|
|
1608
|
-
|
|
1411
|
+
let rules = _rglobSorted(MODULE_STATE.RULES_SOURCE, '*.md')
|
|
1609
1412
|
.filter((p) => _isFile(p))
|
|
1610
1413
|
.filter((p) => rule_in_scope(p, scope, pack_scope))
|
|
1611
1414
|
// ADR-004 manual rules are reference-only — never auto-injected into a
|
|
1612
1415
|
// per-tool tree. Same predicate as generate_rule_symlinks.
|
|
1613
1416
|
.filter((p) => !_is_manual_rule(p));
|
|
1417
|
+
// ADR-236 per-host partition (src/install/ruleLayerPartition.ts). Narrowing the
|
|
1418
|
+
// list is a REMOVAL, because `_clean_modern_dir` below sweeps to `valid`.
|
|
1419
|
+
const kept = new Set(partition_rules_for_dir('.cursor/rules', rules.map((r) => path.basename(r))));
|
|
1420
|
+
rules = rules.filter((r) => kept.has(path.basename(r)));
|
|
1614
1421
|
const stems = rules.map((r) => path.basename(r, '.md'));
|
|
1615
1422
|
const valid = new Set([
|
|
1616
1423
|
...stems.map((s) => `${s}.mdc`),
|
|
@@ -1627,12 +1434,15 @@ export function generate_cursor_mdc_rules(): number {
|
|
|
1627
1434
|
export function generate_windsurf_modern_rules(): number {
|
|
1628
1435
|
const scope = _read_rule_workspaces();
|
|
1629
1436
|
const pack_scope = _read_rule_packs();
|
|
1630
|
-
|
|
1437
|
+
let rules = _rglobSorted(MODULE_STATE.RULES_SOURCE, '*.md')
|
|
1631
1438
|
.filter((p) => _isFile(p))
|
|
1632
1439
|
.filter((p) => rule_in_scope(p, scope, pack_scope))
|
|
1633
1440
|
// ADR-004 manual rules are reference-only — never auto-injected into a
|
|
1634
1441
|
// per-tool tree. Same predicate as generate_rule_symlinks.
|
|
1635
1442
|
.filter((p) => !_is_manual_rule(p));
|
|
1443
|
+
// ADR-236 per-host partition — same shape as the cursor emitter above.
|
|
1444
|
+
const kept = new Set(partition_rules_for_dir('.windsurf/rules', rules.map((r) => path.basename(r))));
|
|
1445
|
+
rules = rules.filter((r) => kept.has(path.basename(r)));
|
|
1636
1446
|
const valid = new Set(rules.map((r) => path.basename(r)));
|
|
1637
1447
|
_clean_modern_dir(_WINDSURF_RULES_DIR(), valid);
|
|
1638
1448
|
for (const rule of rules) {
|
|
@@ -1823,6 +1633,7 @@ function _render_native_model_md(src_md: string, tier: string): string {
|
|
|
1823
1633
|
}
|
|
1824
1634
|
|
|
1825
1635
|
export function generate_claude_skills(active_skill_names: ReadonlySet<string> | null = null): number {
|
|
1636
|
+
if (partitionActive(MODULE_STATE.PROJECT_ROOT)) active_skill_names = new Set<string>();
|
|
1826
1637
|
if (!_exists(MODULE_STATE.SKILLS_SOURCE)) {
|
|
1827
1638
|
process.stderr.write(' ⚠️ dist/agent-src/skills/ not found — skipping skills\n');
|
|
1828
1639
|
return 0;
|
|
@@ -1836,7 +1647,9 @@ export function generate_claude_skills(active_skill_names: ReadonlySet<string> |
|
|
|
1836
1647
|
skills = skills.filter((s) => active_skill_names.has(s));
|
|
1837
1648
|
}
|
|
1838
1649
|
const skill_set = new Set(skills);
|
|
1839
|
-
const command_slugs =
|
|
1650
|
+
const command_slugs = partitionActive(MODULE_STATE.PROJECT_ROOT)
|
|
1651
|
+
? new Set<string>()
|
|
1652
|
+
: new Set([...iterCommands()].map(([, slug]) => slug));
|
|
1840
1653
|
|
|
1841
1654
|
_mkdirp(MODULE_STATE.CLAUDE_SKILLS_DIR);
|
|
1842
1655
|
const auto = _read_model_auto_switch() === 'auto';
|
|
@@ -1932,18 +1745,16 @@ function _nested_command_subpath(source_file: string): string | null {
|
|
|
1932
1745
|
/**
|
|
1933
1746
|
* Project-scope `.claude/commands/<cluster>/<sub>.md` — the colon form.
|
|
1934
1747
|
*
|
|
1935
|
-
* Before this, the package repo's own `.claude/` carried NO commands
|
|
1936
|
-
*
|
|
1937
|
-
*
|
|
1938
|
-
*
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
1941
|
-
* of always-loaded catalog for zero added reach — measured 2026-08-02.
|
|
1748
|
+
* Before this, the package repo's own `.claude/` carried NO commands directory:
|
|
1749
|
+
* every command reached Claude Code as a hyphen-named skill wrapper in
|
|
1750
|
+
* `.claude/skills/`, while the colon form existed only in the user-global tree
|
|
1751
|
+
* written by `install.ts`. Every clustered command was therefore listed TWICE
|
|
1752
|
+
* for anyone with a global install (`roadmap-process-full` AND
|
|
1753
|
+
* `roadmap:process-full`), costing 4,214 GPT tok of catalog — measured 2026-08-02.
|
|
1942
1754
|
*
|
|
1943
1755
|
* Emitting the nested form here lets `generate_claude_commands` stop wrapping
|
|
1944
1756
|
* the clustered commands: one listing, same reachability, and reachability no
|
|
1945
|
-
* longer depends on a global deploy
|
|
1946
|
-
* and user scope by name, so the two copies of `/cluster:sub` collapse.
|
|
1757
|
+
* longer depends on a global deploy. Dedup AND precedence: `commandsWithheld`.
|
|
1947
1758
|
*
|
|
1948
1759
|
* ADR-003 (colon canonical for clusters) and ADR-044 (flat commands stay
|
|
1949
1760
|
* hyphenated) both hold: flat commands are untouched and keep their wrapper.
|
|
@@ -1951,6 +1762,8 @@ function _nested_command_subpath(source_file: string): string | null {
|
|
|
1951
1762
|
export function generate_claude_project_commands(
|
|
1952
1763
|
active_command_slugs: ReadonlySet<string> | null = null,
|
|
1953
1764
|
): number {
|
|
1765
|
+
if (commandsWithheld(MODULE_STATE.PROJECT_ROOT)) active_command_slugs = new Set<string>();
|
|
1766
|
+
// Returns before the stale sweep — invariant, see `commandsWithheld`.
|
|
1954
1767
|
if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
|
|
1955
1768
|
return 0;
|
|
1956
1769
|
}
|
|
@@ -1967,8 +1780,7 @@ export function generate_claude_project_commands(
|
|
|
1967
1780
|
|
|
1968
1781
|
const target_root = _CLAUDE_COMMANDS_DIR();
|
|
1969
1782
|
const valid = new Set(nested.map(([, sub]) => `${sub}.md`));
|
|
1970
|
-
// Sweep stale links first
|
|
1971
|
-
// a dangling `/cluster:sub`.
|
|
1783
|
+
// Sweep stale links first — a renamed, deleted or WITHHELD command must not linger.
|
|
1972
1784
|
if (_exists(target_root)) {
|
|
1973
1785
|
for (const item of _rglobSorted(target_root, '*.md')) {
|
|
1974
1786
|
const rel = _relativeToPosix(item, target_root);
|
|
@@ -1976,6 +1788,7 @@ export function generate_claude_project_commands(
|
|
|
1976
1788
|
fs.unlinkSync(item);
|
|
1977
1789
|
}
|
|
1978
1790
|
}
|
|
1791
|
+
pruneEmptyDirs(target_root); // else the gate counts 40 empty cluster dirs as 40 commands
|
|
1979
1792
|
}
|
|
1980
1793
|
|
|
1981
1794
|
let count = 0;
|
|
@@ -1997,6 +1810,7 @@ export function generate_claude_project_commands(
|
|
|
1997
1810
|
}
|
|
1998
1811
|
|
|
1999
1812
|
export function generate_claude_commands(active_command_slugs: ReadonlySet<string> | null = null): number {
|
|
1813
|
+
if (partitionActive(MODULE_STATE.PROJECT_ROOT)) active_command_slugs = new Set<string>();
|
|
2000
1814
|
if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
|
|
2001
1815
|
process.stderr.write(' ⚠️ src/domains/ not found — skipping commands\n');
|
|
2002
1816
|
return 0;
|
|
@@ -2447,7 +2261,7 @@ export function generate_persona_symlinks(): number {
|
|
|
2447
2261
|
.map((p) => path.basename(p))
|
|
2448
2262
|
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
2449
2263
|
void personas;
|
|
2450
|
-
const
|
|
2264
|
+
const partition = personaPartition(MODULE_STATE.PROJECT_ROOT, direct);
|
|
2451
2265
|
const tool_dirs = _filter_tool_dirs(PERSONA_TOOL_DIRS);
|
|
2452
2266
|
let total = 0;
|
|
2453
2267
|
for (const [tool_dir, rel_prefix] of Object.entries(tool_dirs)) {
|
|
@@ -2455,11 +2269,11 @@ export function generate_persona_symlinks(): number {
|
|
|
2455
2269
|
_mkdirp(target_dir);
|
|
2456
2270
|
for (const item of _iterdirSorted(target_dir)) {
|
|
2457
2271
|
const name = path.basename(item);
|
|
2458
|
-
if (_isSymlink(item) && !
|
|
2272
|
+
if (_isSymlink(item) && !partition.listFor(tool_dir).includes(name) && name !== 'README.md') {
|
|
2459
2273
|
fs.unlinkSync(item);
|
|
2460
2274
|
}
|
|
2461
2275
|
}
|
|
2462
|
-
for (const persona of
|
|
2276
|
+
for (const persona of partition.listFor(tool_dir)) {
|
|
2463
2277
|
const link = path.join(target_dir, persona);
|
|
2464
2278
|
const target = path.join(rel_prefix, persona);
|
|
2465
2279
|
if (_existsOrSymlink(link)) {
|
|
@@ -2469,9 +2283,8 @@ export function generate_persona_symlinks(): number {
|
|
|
2469
2283
|
total += 1;
|
|
2470
2284
|
}
|
|
2471
2285
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
);
|
|
2286
|
+
const per_dir = Object.keys(tool_dirs).map((d) => `${d}=${partition.countFor(d)}`);
|
|
2287
|
+
info(` ✅ Created ${total} persona symlinks — ${per_dir.join(' · ')}${partition.note}`);
|
|
2475
2288
|
return total;
|
|
2476
2289
|
}
|
|
2477
2290
|
|
|
@@ -2671,10 +2484,17 @@ export function project_to_augment(): void {
|
|
|
2671
2484
|
const current = new Set<string>();
|
|
2672
2485
|
let written = 0;
|
|
2673
2486
|
if (_exists(src_rules)) {
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2487
|
+
// ADR-236 per-host partition, ahead of the `use_symlinks` branch so both
|
|
2488
|
+
// writers narrow; the `existing` minus `current` sweep below makes it a
|
|
2489
|
+
// removal. This emitter never had the scope or ADR-004 manual filters
|
|
2490
|
+
// either — why its count is 15 and the symlink trees' 13. Pre-existing.
|
|
2491
|
+
const rules = partition_rules_for_dir(
|
|
2492
|
+
'.augment/rules',
|
|
2493
|
+
_iterdirSorted(src_rules)
|
|
2494
|
+
.filter((p) => p.endsWith('.md') && _isFile(p))
|
|
2495
|
+
.map((p) => path.basename(p))
|
|
2496
|
+
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),
|
|
2497
|
+
);
|
|
2678
2498
|
for (const rule of rules) {
|
|
2679
2499
|
const target = path.join(dst_rules, rule);
|
|
2680
2500
|
if (_isSymlink(target) || _existsOrSymlink(target)) {
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
plan_advisor_swap,
|
|
64
64
|
} from './ai_council/advisors.js';
|
|
65
65
|
import { format_install_hints } from './ai_council/cli_hints.js';
|
|
66
|
+
import { renderSubHelp } from './ai_council/cli_help.js';
|
|
66
67
|
import {
|
|
67
68
|
type AdvisorConfig,
|
|
68
69
|
type CouncilConfig,
|
|
@@ -2507,6 +2508,7 @@ function cmd_run(
|
|
|
2507
2508
|
const [extra_calls, extra_usd] = _consensus_cost_delta(ai_cfg, question.mode, estimates, billable.length);
|
|
2508
2509
|
const [ch_calls, ch_usd] = _chairman_cost_delta(ai_cfg, estimates);
|
|
2509
2510
|
const [pr_extra_calls, pr_extra_usd] = _peer_review_cost_delta(ai_cfg, args, estimates, billable.length);
|
|
2511
|
+
if (!args.confirm) _stdout('council:run · DRY PASS — estimate only, no seat is contacted. Add --confirm to run.\n');
|
|
2510
2512
|
_stdout(
|
|
2511
2513
|
`council:run · mode=${question.mode} · members=${members.length} ` +
|
|
2512
2514
|
`(billable=${billable.length})\n`,
|
|
@@ -3728,10 +3730,8 @@ function _parseArgs(argv: string[]): Args {
|
|
|
3728
3730
|
const rest = argv.slice(1);
|
|
3729
3731
|
const usage = _usageFor(first);
|
|
3730
3732
|
const { positionals, opts, requiredOpts } = _specsFor(first);
|
|
3731
|
-
|
|
3732
|
-
// Sub-help.
|
|
3733
3733
|
if (rest.includes('-h') || rest.includes('--help')) {
|
|
3734
|
-
_stdout(usage);
|
|
3734
|
+
_stdout(renderSubHelp(first, usage, { positionals, opts, requiredOpts }));
|
|
3735
3735
|
process.exitCode = 0;
|
|
3736
3736
|
throw new _ArgExit();
|
|
3737
3737
|
}
|
|
@@ -51,6 +51,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
51
51
|
// (validator → dispatcher for the scope hash, dispatcher → validator for
|
|
52
52
|
// relevance) is safe: neither module calls the other at module-evaluation
|
|
53
53
|
// time, and each CLI entry guard only fires for its own argv[1].
|
|
54
|
+
import { isAcceptanceCriteriaHeading } from './_lib/ac_heading.js';
|
|
54
55
|
import { gitEnv } from './_lib/git_env.js';
|
|
55
56
|
import { completionReviewDisabled } from './_lib/planning_settings.js';
|
|
56
57
|
import { artifactRelevance } from './check_completion_review.js';
|
|
@@ -492,20 +493,11 @@ export function extractAcceptanceCriteria(roadmapText: string): string {
|
|
|
492
493
|
const lines = roadmapText.split('\n');
|
|
493
494
|
let start = -1;
|
|
494
495
|
for (let i = 0; i < lines.length; i++) {
|
|
495
|
-
// Case-insensitive
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
//
|
|
501
|
-
// The end-anchor was the same defect wearing a different mask, and it was
|
|
502
|
-
// LIVE: `\s*$` rejected a heading carrying a qualifier, so
|
|
503
|
-
// `## Acceptance criteria (per phase, on promotion to ready)` and
|
|
504
|
-
// `## Acceptance criteria (anti-dump — the review-s own rule)` extracted
|
|
505
|
-
// nothing. Two roadmaps, measured 2026-08-18 — and they had been counted
|
|
506
|
-
// as "declares no criteria" by the very census that motivated this fix,
|
|
507
|
-
// which is how a matcher tuned to its own measurement hides its misses.
|
|
508
|
-
if (/^##\s+acceptance criteria\b/i.test(lines[i] as string)) {
|
|
496
|
+
// Case-insensitive and NOT end-anchored — both of those were live misses
|
|
497
|
+
// here (case: zcs-close R2 review 2026-08-09; the `(per phase, …)` qualifier:
|
|
498
|
+
// measured 2026-08-18). The predicate now lives in `_lib/ac_heading`, which
|
|
499
|
+
// records all three instances of the defect and why it is shared.
|
|
500
|
+
if (isAcceptanceCriteriaHeading(lines[i] as string)) {
|
|
509
501
|
start = i;
|
|
510
502
|
break;
|
|
511
503
|
}
|