@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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sub-help rendering for the council CLI.
|
|
3
|
+
*
|
|
4
|
+
* Lives here rather than in `council_cli.ts` for a measured reason: that file
|
|
5
|
+
* sits ~2,500 lines above the 1,500-line source ceiling, and
|
|
6
|
+
* `check_source_size_budget` is a shrink-only ratchet — every line added there
|
|
7
|
+
* is a new violation, so a 45-line help renderer reds the build. Extraction is
|
|
8
|
+
* the documented response; raising the baseline is not.
|
|
9
|
+
*
|
|
10
|
+
* WHAT IT FIXES. `council run --help` printed `usage: agent-config council run
|
|
11
|
+
* [-h] ...` and no flag list at all. Measured 2026-08-20: a first council run
|
|
12
|
+
* cost six calls, two of them spent discovering `--confirm` and `--output` by
|
|
13
|
+
* grepping the CLI source. The information was available at runtime all along —
|
|
14
|
+
* the parser's own spec table carries it — it simply was not printed.
|
|
15
|
+
*
|
|
16
|
+
* Rendered from those specs rather than hand-listed on purpose: a hand-written
|
|
17
|
+
* flag list is a second source of truth that drifts the first time a flag is
|
|
18
|
+
* added.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** The shape `council_cli`'s spec table exposes, narrowed to what help needs. */
|
|
22
|
+
export interface HelpSpec {
|
|
23
|
+
readonly positionals: readonly string[];
|
|
24
|
+
readonly opts: readonly { readonly flag: string; readonly takesValue: boolean; readonly choices?: readonly string[] | null }[];
|
|
25
|
+
readonly requiredOpts: readonly string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `usageLine` first (its exact text is asserted elsewhere and must not move),
|
|
30
|
+
* then positionals, then every flag with its value shape and a `[required]`
|
|
31
|
+
* marker where the parser demands one.
|
|
32
|
+
*/
|
|
33
|
+
export function renderSubHelp(_cmd: string, usageLine: string, spec: HelpSpec): string {
|
|
34
|
+
const lines: string[] = [usageLine.trimEnd()];
|
|
35
|
+
if (spec.positionals.length > 0) {
|
|
36
|
+
lines.push('', 'positional arguments:');
|
|
37
|
+
for (const p of spec.positionals) {
|
|
38
|
+
lines.push(` ${p}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (spec.opts.length > 0) {
|
|
42
|
+
lines.push('', 'options:');
|
|
43
|
+
const required = new Set(spec.requiredOpts);
|
|
44
|
+
for (const o of spec.opts) {
|
|
45
|
+
const value = o.takesValue
|
|
46
|
+
? o.choices && o.choices.length > 0
|
|
47
|
+
? ` {${o.choices.join(',')}}`
|
|
48
|
+
: ' VALUE'
|
|
49
|
+
: '';
|
|
50
|
+
lines.push(` ${o.flag}${value}${required.has(o.flag) ? ' [required]' : ''}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
lines.push('');
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
}
|
|
@@ -16,12 +16,19 @@
|
|
|
16
16
|
* --regen-area-readme <area> rewrite docs/adrs/<area>/README.md. Idempotent.
|
|
17
17
|
*
|
|
18
18
|
* Historical quirks are preserved deliberately — tests and downstream consumers pin the exact behaviour.
|
|
19
|
+
*
|
|
20
|
+
* Record metadata is read by `read_adr_meta`: the shared frontmatter reader
|
|
21
|
+
* (`_lib/adr_frontmatter`) when a `---` block is present, the transitional
|
|
22
|
+
* blockquote header otherwise. The seven per-area records carried only the
|
|
23
|
+
* blockquote until 2026-08-21, so both paths have to work while the two
|
|
24
|
+
* surfaces overlap.
|
|
19
25
|
*/
|
|
20
26
|
import * as fs from 'node:fs';
|
|
21
27
|
import * as path from 'node:path';
|
|
22
28
|
import process from 'node:process';
|
|
23
29
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
24
30
|
|
|
31
|
+
import { readAdrFrontmatterScalars } from './_lib/adr_frontmatter.js';
|
|
25
32
|
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
26
33
|
|
|
27
34
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -89,10 +96,26 @@ export function unlistedAreaDirs(adrsRoot: string): string[] {
|
|
|
89
96
|
|
|
90
97
|
// ^(\d{4})-([a-z0-9-]+)\.md$
|
|
91
98
|
const NAMED = /^(\d{4})-([a-z0-9-]+)\.md$/;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Metadata keys this script recognises in the transitional blockquote header.
|
|
102
|
+
*
|
|
103
|
+
* An allowlist rather than "every `Key: value` pair in a blockquote", because
|
|
104
|
+
* the blockquote block also carries prose that happens to contain colons — the
|
|
105
|
+
* `Roadmap:` pointer, a `Contract:` link, and (on `memory/0001`) a whole
|
|
106
|
+
* supersession banner. Harvesting those would put link markup into the README
|
|
107
|
+
* table. Keys are matched case-insensitively with spaces folded to `_`.
|
|
108
|
+
*/
|
|
109
|
+
const BLOCKQUOTE_KEYS = new Set([
|
|
110
|
+
'adr',
|
|
111
|
+
'area',
|
|
112
|
+
'status',
|
|
113
|
+
'date',
|
|
114
|
+
'decision',
|
|
115
|
+
'supersedes',
|
|
116
|
+
'superseded_by',
|
|
117
|
+
'type',
|
|
118
|
+
]);
|
|
96
119
|
|
|
97
120
|
interface AdrEntry {
|
|
98
121
|
num: string;
|
|
@@ -129,24 +152,87 @@ function _globMdSorted(dir: string): string[] {
|
|
|
129
152
|
return out;
|
|
130
153
|
}
|
|
131
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Read the leading `---` frontmatter block, `{}` when there is none.
|
|
157
|
+
*
|
|
158
|
+
* Delegates to the one shared reader (`_lib/adr_frontmatter`) rather than
|
|
159
|
+
* keeping this script's own regex: the tree carried three divergent ADR
|
|
160
|
+
* frontmatter parsers plus this one, and the two nested axes (`provenance`,
|
|
161
|
+
* `evidence`) are exactly the shape a `^([a-z_]+):\s*(.+?)$` regex reads as
|
|
162
|
+
* absent.
|
|
163
|
+
*
|
|
164
|
+
* What is preserved: the `{}`-on-absent return, and the scalar keys this
|
|
165
|
+
* script's callers read.
|
|
166
|
+
*
|
|
167
|
+
* What changed, stated because "preserved" was claimed for it and is false:
|
|
168
|
+
* the removed `_stripChars(v, ' "\'')` stripped repeated and unbalanced quote
|
|
169
|
+
* and space characters from both ends (Python `str.strip` semantics), while the
|
|
170
|
+
* shared reader's `stripQuotes` is `/^["'](.*)["']$/` — exactly one balanced
|
|
171
|
+
* pair. Two inputs now differ: `key: "value` keeps its opening quote (was
|
|
172
|
+
* `value`), and `key: ''v''` yields `'v'` (was `v`). No record in the corpus
|
|
173
|
+
* exercises either shape today, and the old behaviour is deliberately NOT
|
|
174
|
+
* restored — one shared reader beats a bug-compatible local copy.
|
|
175
|
+
*/
|
|
132
176
|
export function parse_fm(text: string): Record<string, string> {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
177
|
+
return readAdrFrontmatterScalars(text) ?? {};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Read the transitional blockquote metadata header.
|
|
182
|
+
*
|
|
183
|
+
* The seven per-area records predate the frontmatter contract and carry their
|
|
184
|
+
* metadata as a blockquote line instead:
|
|
185
|
+
*
|
|
186
|
+
* ```
|
|
187
|
+
* > Area: `router` · Status: accepted · Date: 2026-05-16 · Type: retrospective
|
|
188
|
+
* ```
|
|
189
|
+
*
|
|
190
|
+
* Two shapes have to be handled and both occur in the tree. Several fields
|
|
191
|
+
* share one line separated by `·`, so the split is on the separator first and
|
|
192
|
+
* the first `:` second. And the value is often wrapped in backticks
|
|
193
|
+
* (`` `router` ``), which are stripped — a table cell showing `` `accepted` ``
|
|
194
|
+
* would not match what the frontmatter path produces for the same record.
|
|
195
|
+
*
|
|
196
|
+
* Scanning stops at the first `## ` heading: everything above it is the
|
|
197
|
+
* metadata header, everything below is body prose whose blockquotes are
|
|
198
|
+
* quotations, not metadata.
|
|
199
|
+
*/
|
|
200
|
+
export function parse_blockquote_meta(text: string): Record<string, string> {
|
|
137
201
|
const out: Record<string, string> = {};
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
202
|
+
for (const raw of text.split('\n')) {
|
|
203
|
+
const line = raw.trim();
|
|
204
|
+
if (line.startsWith('## ')) break;
|
|
205
|
+
if (!line.startsWith('>')) continue;
|
|
206
|
+
for (const part of line.replace(/^>\s*/, '').split('·')) {
|
|
207
|
+
const idx = part.indexOf(':');
|
|
208
|
+
if (idx <= 0) continue;
|
|
209
|
+
const key = part.slice(0, idx).trim().toLowerCase().replace(/\s+/g, '_');
|
|
210
|
+
if (!BLOCKQUOTE_KEYS.has(key)) continue;
|
|
211
|
+
const value = _stripChars(part.slice(idx + 1), ' "\'`');
|
|
212
|
+
if (value === '') continue;
|
|
213
|
+
// First occurrence wins — a later body line never overwrites the header.
|
|
214
|
+
if (!(key in out)) out[key] = value;
|
|
215
|
+
}
|
|
146
216
|
}
|
|
147
217
|
return out;
|
|
148
218
|
}
|
|
149
219
|
|
|
220
|
+
/**
|
|
221
|
+
* One record's metadata: frontmatter when present, the blockquote header
|
|
222
|
+
* otherwise.
|
|
223
|
+
*
|
|
224
|
+
* Frontmatter wins outright rather than merging. During the transition a record
|
|
225
|
+
* carries both, and the blockquote is explicitly the human-readable duplicate —
|
|
226
|
+
* merging would make the authoritative surface depend on which of the two
|
|
227
|
+
* happened to mention a field, and a stale duplicate would then be able to
|
|
228
|
+
* contribute a value the frontmatter had already corrected.
|
|
229
|
+
*/
|
|
230
|
+
export function read_adr_meta(text: string): Record<string, string> {
|
|
231
|
+
const fm = readAdrFrontmatterScalars(text);
|
|
232
|
+
if (fm !== null && Object.keys(fm).length > 0) return fm;
|
|
233
|
+
return parse_blockquote_meta(text);
|
|
234
|
+
}
|
|
235
|
+
|
|
150
236
|
/** Mirror Python `str.strip(chars)`. */
|
|
151
237
|
function _stripChars(s: string, chars: string): string {
|
|
152
238
|
let start = 0;
|
|
@@ -193,7 +279,7 @@ export function scan_area(area: string): [AdrEntry[], string[]] {
|
|
|
193
279
|
errs.push(`${area}/${base}: filename does not match NNNN-<slug>.md`);
|
|
194
280
|
continue;
|
|
195
281
|
}
|
|
196
|
-
const fm =
|
|
282
|
+
const fm = read_adr_meta(fs.readFileSync(p, 'utf-8'));
|
|
197
283
|
adrs.push({ num: m[1] as string, slug: m[2] as string, path: base, ...fm });
|
|
198
284
|
}
|
|
199
285
|
// Gap check.
|
|
@@ -29,7 +29,14 @@
|
|
|
29
29
|
* - post_tool_use → inspect tool + command, record verifications
|
|
30
30
|
* - stop → record stop fired (claim-done window)
|
|
31
31
|
*
|
|
32
|
-
* Output: `agents/state/verify-before-complete
|
|
32
|
+
* Output: `agents/state/verify-before-complete/<sha256(session_id)>.json`
|
|
33
|
+
* — ONE FILE PER SESSION. It was one file per project root until 2026-08-20,
|
|
34
|
+
* which under this repo's worktree workflow (`CLAUDE_PROJECT_DIR` resolves to
|
|
35
|
+
* the PARENT checkout) meant one file across every concurrent run: a
|
|
36
|
+
* neighbour's CI witness and verification counters became this run's, and the
|
|
37
|
+
* in-file session-boundary reset in `_update` turned into the damage rather
|
|
38
|
+
* than the defense, because two live runs then clear each other in a loop.
|
|
39
|
+
* Consumers address it via `statePathFor`, never a path literal.
|
|
33
40
|
* {
|
|
34
41
|
* "schema_version": 1,
|
|
35
42
|
* "session_id": "<str>",
|
|
@@ -49,14 +56,79 @@ import fs from "node:fs";
|
|
|
49
56
|
import path from "node:path";
|
|
50
57
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
51
58
|
|
|
52
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
has_stable_session_id,
|
|
61
|
+
prune_legacy_state_file,
|
|
62
|
+
prune_stale_session_states,
|
|
63
|
+
session_state_file,
|
|
64
|
+
update_json_under_lock,
|
|
65
|
+
} from "./hooks/state_io.js";
|
|
53
66
|
import { readHookStdin } from "./hooks/hook_stdin.js";
|
|
54
67
|
|
|
55
68
|
// NOTE: the Python docstring says `agents/runtime/state/`, but the code
|
|
56
69
|
// constant is `agents/state/`. Replicated verbatim — latent docstring/code
|
|
57
70
|
// divergence in the retired Python implementation (ADR-200 § replicate latent bugs).
|
|
71
|
+
//
|
|
72
|
+
// PRE-SPLIT PATH. Nothing reads it after the per-session split below;
|
|
73
|
+
// `prune_legacy_state_file` removes it once this version owns the tree. Kept
|
|
74
|
+
// exported because `prune_legacy_state_file` needs to name the path it deletes,
|
|
75
|
+
// and because a reader that still resolves it should get a compile-visible
|
|
76
|
+
// symbol rather than a silently stale path literal. It is NOT kept for an
|
|
77
|
+
// "older bundle still writing during an upgrade" — that phrasing was copied
|
|
78
|
+
// here unchecked and is corrected at `state_io.prune_legacy_state_file`, which
|
|
79
|
+
// carries the measured deployment shape and the one narrow window that remains.
|
|
58
80
|
export const STATE_FILE = path.join("agents", "state", "verify-before-complete.json");
|
|
59
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Per-SESSION state, one file each.
|
|
84
|
+
*
|
|
85
|
+
* WHY, in one sentence: the single file above is shared by every concurrent
|
|
86
|
+
* session under one project root — which in this repo's worktree workflow is
|
|
87
|
+
* the PARENT checkout — so a neighbouring run's CI witness and verification
|
|
88
|
+
* counters became this run's.
|
|
89
|
+
*
|
|
90
|
+
* The in-file session-boundary reset in `_update` is NOT the fix and is the
|
|
91
|
+
* reason this was easy to miss. It is written for SEQUENTIAL sessions: notice a
|
|
92
|
+
* foreign `session_id`, clear the session-scoped counters, carry on. Under
|
|
93
|
+
* CONCURRENT sessions that same code is the damage — each run reads the other's
|
|
94
|
+
* id, resets, and writes, so two sessions erase each other's verification
|
|
95
|
+
* evidence in a loop. The direction of the loss is toward FORGETTING a
|
|
96
|
+
* verification that did happen, which then reads as "not verified this turn".
|
|
97
|
+
*
|
|
98
|
+
* The reset stays, but NOT for the reasons this comment first gave. It said the
|
|
99
|
+
* reset "still covers the id-less bucket and a legacy file", and a cross-model
|
|
100
|
+
* review (2026-08-20, both seats) showed neither is reachable: an id-less
|
|
101
|
+
* envelope returns from `run()` before `_update` is ever called, and the legacy
|
|
102
|
+
* file is never LOADED any more — only deleted. Writing an unreachable
|
|
103
|
+
* justification next to retained code is how dead logic survives review, so the
|
|
104
|
+
* real one is stated instead.
|
|
105
|
+
*
|
|
106
|
+
* Its one reachable case is a file at THIS session's digest path carrying
|
|
107
|
+
* somebody else's `session_id` — a copy, a restore, a hand-edit, or a buggy
|
|
108
|
+
* writer. That is integrity recovery on the producer side, and it pairs with the
|
|
109
|
+
* consumer side refusing the same file outright (`owns_session_state`). Both
|
|
110
|
+
* halves are needed: the producer cannot refuse to run, and the consumer cannot
|
|
111
|
+
* repair. `before_complete_session_isolation.test.ts` reaches it directly.
|
|
112
|
+
*
|
|
113
|
+
* Rationale, the digest-not-sanitiser property, and the claim-then-revalidate
|
|
114
|
+
* prune all live once in `hooks/state_io.ts` § Per-session concern state — this
|
|
115
|
+
* is the second concern to need them, which is what made sharing them right.
|
|
116
|
+
*/
|
|
117
|
+
export const STATE_DIR = path.join("agents", "state", "verify-before-complete");
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Days after which an untouched session's state is pruned.
|
|
121
|
+
*
|
|
122
|
+
* Matches the language hook and the council session-artefact window — a
|
|
123
|
+
* convention match, not a measurement, and stated as such.
|
|
124
|
+
*/
|
|
125
|
+
export const STATE_RETENTION_DAYS = 7;
|
|
126
|
+
|
|
127
|
+
/** Path of one session's state file. */
|
|
128
|
+
export function statePathFor(session_id: string): string {
|
|
129
|
+
return session_state_file(STATE_DIR, session_id);
|
|
130
|
+
}
|
|
131
|
+
|
|
60
132
|
// Tool names across platforms whose `command` / `tool_input.command` field
|
|
61
133
|
// carries a shell command we can inspect. Edit tools are deliberately
|
|
62
134
|
// excluded — they cannot run verification.
|
|
@@ -125,26 +197,6 @@ function _empty_state(): StateDict {
|
|
|
125
197
|
};
|
|
126
198
|
}
|
|
127
199
|
|
|
128
|
-
function _load_state(target: string): StateDict {
|
|
129
|
-
let isFile = false;
|
|
130
|
-
try {
|
|
131
|
-
isFile = fs.statSync(target).isFile();
|
|
132
|
-
} catch {
|
|
133
|
-
isFile = false;
|
|
134
|
-
}
|
|
135
|
-
if (!isFile) {
|
|
136
|
-
return _empty_state();
|
|
137
|
-
}
|
|
138
|
-
try {
|
|
139
|
-
const decoded = JSON.parse(fs.readFileSync(target, "utf-8")) as unknown;
|
|
140
|
-
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
141
|
-
return { ..._empty_state(), ...(decoded as StateDict) };
|
|
142
|
-
}
|
|
143
|
-
} catch {
|
|
144
|
-
// fall through
|
|
145
|
-
}
|
|
146
|
-
return _empty_state();
|
|
147
|
-
}
|
|
148
200
|
|
|
149
201
|
/** Return [tool_name, command_text] from a tool-event payload. */
|
|
150
202
|
function _extract_command(payload: StateDict): [string | null, string | null] {
|
|
@@ -424,19 +476,92 @@ export function run(
|
|
|
424
476
|
}
|
|
425
477
|
|
|
426
478
|
const event = (envelope["event"] || "") as string;
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
479
|
+
const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
480
|
+
|
|
481
|
+
// No stable identity → persist NOTHING. Sanitising an empty id into a shared
|
|
482
|
+
// literal is the original cross-session defect restored in the one case with
|
|
483
|
+
// no guard left (see `state_io` § has_stable_session_id).
|
|
484
|
+
//
|
|
485
|
+
// What that costs, and it is worse than the word this comment first used: on a
|
|
486
|
+
// host that sends no `session_id` this concern records no evidence at all, so
|
|
487
|
+
// `readCiSettled` returns "nothing observed" and the turn-end gate's completion
|
|
488
|
+
// detector never fires. The first version called that "the SAFE direction —
|
|
489
|
+
// under-refusing". A cross-model review (2026-08-20, both seats) rejected the
|
|
490
|
+
// word: for a BLOCKING gate, not refusing is fail-OPEN. A premature completion
|
|
491
|
+
// claim over unsettled CI passes unchallenged. This is DEGRADED ENFORCEMENT, and
|
|
492
|
+
// naming it "safe" hides the loss behind a reassuring adjective.
|
|
493
|
+
//
|
|
494
|
+
// It is still the right call among the options available, which is a different
|
|
495
|
+
// claim and the only one the evidence supports: the alternative is one shared
|
|
496
|
+
// file whose contents belong to whichever concurrent run wrote last, i.e. a
|
|
497
|
+
// gate that refuses or clears on somebody else's evidence. A gate that goes
|
|
498
|
+
// quiet is recoverable; a gate that acts on a foreign witness is not.
|
|
499
|
+
// Every host this suite binds `post_tool_use` on sends a `session_id`
|
|
500
|
+
// (`hook_manifest.yaml` platforms × `native_event_aliases`), so the degraded
|
|
501
|
+
// path is a fallback rather than a supported mode — but it is not verified per
|
|
502
|
+
// host, and this comment does not claim it is.
|
|
503
|
+
if (!has_stable_session_id(session_id)) {
|
|
504
|
+
if (verbose) {
|
|
505
|
+
process.stderr.write(
|
|
506
|
+
"verify-before-complete-hook: no session_id — running stateless, nothing recorded\n",
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return 0;
|
|
510
|
+
}
|
|
430
511
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
512
|
+
const target = path.join(consumer_root, statePathFor(session_id));
|
|
513
|
+
|
|
514
|
+
// LOAD → UPDATE → PUBLISH under ONE lock, not three separate steps.
|
|
515
|
+
//
|
|
516
|
+
// This was `_load_state` / `_update` / `atomic_write_json`, which makes the
|
|
517
|
+
// publish atomic and leaves the transaction racy. A cross-model review
|
|
518
|
+
// (2026-08-20, both seats) named the interleaving, and this host runs tool
|
|
519
|
+
// calls in parallel, so it is reachable rather than theoretical: two
|
|
520
|
+
// `post_tool_use` invocations for the SAME session both load the counter at
|
|
521
|
+
// N, both compute N+1, both publish N+1, and one verification is lost. The
|
|
522
|
+
// per-session split does nothing about it — the two failures are independent,
|
|
523
|
+
// and closing the cross-session one made this one easier to mistake for
|
|
524
|
+
// solved.
|
|
525
|
+
//
|
|
526
|
+
// `state` is captured for the verbose line below; the value that lands is the
|
|
527
|
+
// one computed inside the lock, from state read inside the lock.
|
|
528
|
+
let state: StateDict = {};
|
|
529
|
+
// Three-state result (state_io § Three states, not two). This mutator NEVER
|
|
530
|
+
// returns null, so `skipped` is unreachable here — but it is handled with
|
|
531
|
+
// `written` rather than lumped with `failed`, because the two have opposite
|
|
532
|
+
// meanings for this caller: a decline means the state needed no change, a
|
|
533
|
+
// failure means the state on disk is not what this run computed. Writing
|
|
534
|
+
// `!== "written"` would have made a future mutator that learns to decline
|
|
535
|
+
// silently stop recording, which is the failure this API change exists to
|
|
536
|
+
// make impossible to write by accident.
|
|
537
|
+
const outcome = update_json_under_lock<StateDict>(target, (loaded) => {
|
|
538
|
+
// `_empty_state()` UNDER the loaded value, which is what the `_load_state`
|
|
539
|
+
// this replaced did (`{ ..._empty_state(), ...decoded }`). Dropping it would
|
|
540
|
+
// have been a silent regression that the suite could not see: `_asInt`
|
|
541
|
+
// treats a missing counter as 0 and the `=== true` guards treat a missing
|
|
542
|
+
// flag as false, so every assertion would still pass while `schema_version`
|
|
543
|
+
// and an explicit `ci_last: null` vanished from a freshly created file.
|
|
544
|
+
state = _update({ ..._empty_state(), ...loaded } as StateDict, event, envelope);
|
|
545
|
+
return state;
|
|
546
|
+
});
|
|
547
|
+
if (outcome === "failed") {
|
|
434
548
|
if (verbose) {
|
|
435
549
|
process.stderr.write("verify-before-complete-hook: state write failed\n");
|
|
436
550
|
}
|
|
437
551
|
return 0;
|
|
438
552
|
}
|
|
439
553
|
|
|
554
|
+
// Housekeeping on the once-per-turn events only — never on `post_tool_use`,
|
|
555
|
+
// which fires many times per turn, and never before the write above.
|
|
556
|
+
if (event === "session_start" || event === "user_prompt_submit") {
|
|
557
|
+
prune_legacy_state_file(path.join(consumer_root, STATE_FILE));
|
|
558
|
+
prune_stale_session_states(
|
|
559
|
+
path.join(consumer_root, STATE_DIR),
|
|
560
|
+
Date.now(),
|
|
561
|
+
STATE_RETENTION_DAYS,
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
440
565
|
if (verbose) {
|
|
441
566
|
process.stderr.write(
|
|
442
567
|
`verify-before-complete-hook: event=${event} ` +
|
|
@@ -35,6 +35,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
35
35
|
|
|
36
36
|
import { parse as parseYaml } from 'yaml';
|
|
37
37
|
|
|
38
|
+
import { PinnedRepoError, pristine_tree_for } from './_lib/bench_ab_pinned_repo.js';
|
|
38
39
|
import { medianComplexityPerChangedFunction } from './_lib/bench_ab_complexity.js';
|
|
39
40
|
import { changed_files, diff_line_counts } from './_lib/bench_ab_scoring_v2.js';
|
|
40
41
|
|
|
@@ -63,15 +64,15 @@ function _dictOr(v: unknown): Dict {
|
|
|
63
64
|
* trial to `skipped_reason: 'task id not in the corpus'`, which is a reportable
|
|
64
65
|
* state, rather than aborting a re-score that may still cover most of the report.
|
|
65
66
|
*/
|
|
66
|
-
function loadCorpusFixtures(corpusPath: string | null): Map<string,
|
|
67
|
-
const byId = new Map<string,
|
|
67
|
+
function loadCorpusFixtures(corpusPath: string | null): Map<string, Dict> {
|
|
68
|
+
const byId = new Map<string, Dict>();
|
|
68
69
|
if (!corpusPath || !fs.existsSync(corpusPath)) return byId;
|
|
69
70
|
try {
|
|
70
71
|
const corpus = _dictOr(parseYaml(fs.readFileSync(corpusPath, 'utf8'), { version: '1.1' }));
|
|
71
72
|
const tasks = Array.isArray(corpus['tasks']) ? (corpus['tasks'] as Dict[]) : [];
|
|
72
73
|
for (const t of tasks) {
|
|
73
|
-
if (t['id'] !== undefined && t['fixture'] !== undefined) {
|
|
74
|
-
byId.set(String(t['id']),
|
|
74
|
+
if (t['id'] !== undefined && (t['fixture'] !== undefined || t['repo'] !== undefined)) {
|
|
75
|
+
byId.set(String(t['id']), t);
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
} catch {
|
|
@@ -109,7 +110,7 @@ export async function rescoreReport(
|
|
|
109
110
|
|
|
110
111
|
for (const rec of records) {
|
|
111
112
|
const taskId = String(rec['id'] ?? '<unknown>');
|
|
112
|
-
const
|
|
113
|
+
const corpusTask = byId.get(taskId) ?? null;
|
|
113
114
|
const arms = _dictOr(rec['arms']);
|
|
114
115
|
for (const [arm, runsRaw] of Object.entries(arms)) {
|
|
115
116
|
const runs = Array.isArray(runsRaw) ? (runsRaw as Dict[]) : [];
|
|
@@ -134,12 +135,31 @@ export async function rescoreReport(
|
|
|
134
135
|
out.push(row);
|
|
135
136
|
continue;
|
|
136
137
|
}
|
|
137
|
-
if (
|
|
138
|
+
if (corpusTask === null) {
|
|
138
139
|
row.skipped_reason = 'task id not in the corpus';
|
|
139
140
|
out.push(row);
|
|
140
141
|
continue;
|
|
141
142
|
}
|
|
142
|
-
|
|
143
|
+
// Delta #9: a pinned task's pristine tree is a local cache entry, and
|
|
144
|
+
// this re-scorer is `offlineOnly` on purpose — a cold cache reports
|
|
145
|
+
// "not materialised" rather than fetching a repository during what is
|
|
146
|
+
// meant to be an offline re-score of a finished sweep. A malformed pin
|
|
147
|
+
// is reported per trial, never thrown: one bad corpus entry must not
|
|
148
|
+
// abort a re-score that still covers every other trial.
|
|
149
|
+
let fixture: string | null;
|
|
150
|
+
try {
|
|
151
|
+
fixture = pristine_tree_for(corpusTask, { fixturesRoot, offlineOnly: true });
|
|
152
|
+
} catch (err) {
|
|
153
|
+
row.skipped_reason =
|
|
154
|
+
err instanceof PinnedRepoError ? `invalid pin: ${err.message}` : 'invalid corpus task';
|
|
155
|
+
out.push(row);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (fixture === null) {
|
|
159
|
+
row.skipped_reason = 'pinned repo not materialised locally';
|
|
160
|
+
out.push(row);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
143
163
|
if (!fs.existsSync(fixture)) {
|
|
144
164
|
row.skipped_reason = 'fixture missing on disk';
|
|
145
165
|
out.push(row);
|