@danmoisan/drm-copilot-mcp 1.1.12 → 1.1.14
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/out/mcp-server.js +30 -78
- package/package.json +2 -2
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +18 -1
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +16 -8
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +27 -27
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +43 -6
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +486 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +5 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +446 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -2
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +5 -5
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +11 -0
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +32 -17
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +39 -10
- package/resources/claude-customizations/config/blast-radius.json +14 -1
- package/resources/claude-customizations/pack-manifests/core.json +2 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-resolution.ps1 +493 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +189 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +27 -27
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +2 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +11 -0
package/out/mcp-server.js
CHANGED
|
@@ -19728,7 +19728,10 @@ var CARRIED_KEYS = [
|
|
|
19728
19728
|
"shared_surface_globs",
|
|
19729
19729
|
"over_breadth_fraction",
|
|
19730
19730
|
"mandate_reads",
|
|
19731
|
-
"mergeable_paths"
|
|
19731
|
+
"mergeable_paths",
|
|
19732
|
+
"conflict_tolerance",
|
|
19733
|
+
"write_intent_extraction",
|
|
19734
|
+
"path_roots"
|
|
19732
19735
|
];
|
|
19733
19736
|
var BlastRadiusDeriveError = class extends Error {
|
|
19734
19737
|
/** Destination-relative path of the document that failed to parse. */
|
|
@@ -19835,6 +19838,9 @@ function deriveDestinationModuleMap(observations, sourceDocumentText) {
|
|
|
19835
19838
|
shared_surface_globs: source[CARRIED_KEYS[2]],
|
|
19836
19839
|
mandate_reads: source[CARRIED_KEYS[4]],
|
|
19837
19840
|
mergeable_paths: source[CARRIED_KEYS[5]],
|
|
19841
|
+
conflict_tolerance: source[CARRIED_KEYS[6]],
|
|
19842
|
+
write_intent_extraction: source[CARRIED_KEYS[7]],
|
|
19843
|
+
path_roots: source[CARRIED_KEYS[8]],
|
|
19838
19844
|
modules,
|
|
19839
19845
|
over_breadth_fraction: source[CARRIED_KEYS[3]]
|
|
19840
19846
|
};
|
|
@@ -32067,6 +32073,15 @@ function formatList(values, emptyText) {
|
|
|
32067
32073
|
}
|
|
32068
32074
|
return valuesList.map((item) => `- ${item}`).join("\n");
|
|
32069
32075
|
}
|
|
32076
|
+
function compareCodePoint(left, right) {
|
|
32077
|
+
if (left < right) {
|
|
32078
|
+
return -1;
|
|
32079
|
+
}
|
|
32080
|
+
if (left > right) {
|
|
32081
|
+
return 1;
|
|
32082
|
+
}
|
|
32083
|
+
return 0;
|
|
32084
|
+
}
|
|
32070
32085
|
|
|
32071
32086
|
// ../../extensions/drm-copilot/src/lib/pr-context/git-client.ts
|
|
32072
32087
|
var GitClient = class {
|
|
@@ -32261,15 +32276,6 @@ function extractClosingNumbers(raw) {
|
|
|
32261
32276
|
function sortedSet(values) {
|
|
32262
32277
|
return [...new Set(values)].sort(compareCodePoint);
|
|
32263
32278
|
}
|
|
32264
|
-
function compareCodePoint(left, right) {
|
|
32265
|
-
if (left < right) {
|
|
32266
|
-
return -1;
|
|
32267
|
-
}
|
|
32268
|
-
if (left > right) {
|
|
32269
|
-
return 1;
|
|
32270
|
-
}
|
|
32271
|
-
return 0;
|
|
32272
|
-
}
|
|
32273
32279
|
function authorLogin(record3, key) {
|
|
32274
32280
|
const raw = record3[key];
|
|
32275
32281
|
if (isRecord5(raw)) {
|
|
@@ -32836,9 +32842,9 @@ function selectPendingPrimary(options) {
|
|
|
32836
32842
|
}
|
|
32837
32843
|
function buildCloseCandidatesSection(params) {
|
|
32838
32844
|
const { verified, authorAsserted, referenced, verifiedReason, authorReason } = params;
|
|
32839
|
-
const authorAutoClose = [...new Set(authorAsserted)].sort(
|
|
32845
|
+
const authorAutoClose = [...new Set(authorAsserted)].sort(compareCodePoint);
|
|
32840
32846
|
const closing = /* @__PURE__ */ new Set([...verified, ...authorAsserted]);
|
|
32841
|
-
const referencedOnly = [...new Set(referenced)].filter((ref) => !closing.has(ref)).sort(
|
|
32847
|
+
const referencedOnly = [...new Set(referenced)].filter((ref) => !closing.has(ref)).sort(compareCodePoint);
|
|
32842
32848
|
return [
|
|
32843
32849
|
section("Close candidates"),
|
|
32844
32850
|
"Auto-close issues (verified from GitHub PR metadata):",
|
|
@@ -32885,15 +32891,6 @@ ${AUTOCLOSE_UNVERIFIED_ANNOTATION}`;
|
|
|
32885
32891
|
"\n"
|
|
32886
32892
|
);
|
|
32887
32893
|
}
|
|
32888
|
-
function compareCodePoint2(left, right) {
|
|
32889
|
-
if (left < right) {
|
|
32890
|
-
return -1;
|
|
32891
|
-
}
|
|
32892
|
-
if (left > right) {
|
|
32893
|
-
return 1;
|
|
32894
|
-
}
|
|
32895
|
-
return 0;
|
|
32896
|
-
}
|
|
32897
32894
|
|
|
32898
32895
|
// ../../extensions/drm-copilot/src/lib/pr-context/render-pr-helpers.ts
|
|
32899
32896
|
var DEFAULT_BASE_CANDIDATES = [
|
|
@@ -32961,7 +32958,7 @@ function extensionSummary(files) {
|
|
|
32961
32958
|
const ext = suffix2 ? suffix2 : "(noext)";
|
|
32962
32959
|
counts.set(ext, (counts.get(ext) ?? 0) + 1);
|
|
32963
32960
|
}
|
|
32964
|
-
const lines = [...counts.keys()].sort(
|
|
32961
|
+
const lines = [...counts.keys()].sort(compareCodePoint).map((key) => `${padCount(counts.get(key))} ${key}`);
|
|
32965
32962
|
return lines.join("\n");
|
|
32966
32963
|
}
|
|
32967
32964
|
function extractIssueReferences(text2) {
|
|
@@ -32988,7 +32985,7 @@ function extractMergePrNumbers(subjects) {
|
|
|
32988
32985
|
numbers.add(`#${match[1]}`);
|
|
32989
32986
|
}
|
|
32990
32987
|
}
|
|
32991
|
-
return [...numbers].sort(
|
|
32988
|
+
return [...numbers].sort(compareCodePoint);
|
|
32992
32989
|
}
|
|
32993
32990
|
function summarizeConventionalCommits(subjects) {
|
|
32994
32991
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -33052,15 +33049,6 @@ function padName(name) {
|
|
|
33052
33049
|
function isDigits(value) {
|
|
33053
33050
|
return value.length > 0 && /^\d+$/u.test(value);
|
|
33054
33051
|
}
|
|
33055
|
-
function compareCodePoint3(left, right) {
|
|
33056
|
-
if (left < right) {
|
|
33057
|
-
return -1;
|
|
33058
|
-
}
|
|
33059
|
-
if (left > right) {
|
|
33060
|
-
return 1;
|
|
33061
|
-
}
|
|
33062
|
-
return 0;
|
|
33063
|
-
}
|
|
33064
33052
|
function splitLines4(value) {
|
|
33065
33053
|
if (value === "") {
|
|
33066
33054
|
return [];
|
|
@@ -33287,16 +33275,7 @@ function errorMessage(exc) {
|
|
|
33287
33275
|
return exc instanceof Error ? exc.message : String(exc);
|
|
33288
33276
|
}
|
|
33289
33277
|
function sortedSet2(values) {
|
|
33290
|
-
return [...new Set(values)].sort(
|
|
33291
|
-
}
|
|
33292
|
-
function compareCodePoint4(left, right) {
|
|
33293
|
-
if (left < right) {
|
|
33294
|
-
return -1;
|
|
33295
|
-
}
|
|
33296
|
-
if (left > right) {
|
|
33297
|
-
return 1;
|
|
33298
|
-
}
|
|
33299
|
-
return 0;
|
|
33278
|
+
return [...new Set(values)].sort(compareCodePoint);
|
|
33300
33279
|
}
|
|
33301
33280
|
function splitLines5(value) {
|
|
33302
33281
|
if (value === "") {
|
|
@@ -33384,7 +33363,7 @@ function readText(fs12, path16) {
|
|
|
33384
33363
|
return fs12.exists(path16) ? fs12.readTextFile(path16) : "";
|
|
33385
33364
|
}
|
|
33386
33365
|
function latestGlobPath(fs12, directory, pattern) {
|
|
33387
|
-
const matches = [...fs12.glob(directory, pattern)].sort(
|
|
33366
|
+
const matches = [...fs12.glob(directory, pattern)].sort(compareCodePoint);
|
|
33388
33367
|
return matches.length > 0 ? matches[matches.length - 1] : null;
|
|
33389
33368
|
}
|
|
33390
33369
|
function verificationText(planText) {
|
|
@@ -33428,7 +33407,7 @@ function parseReadinessValue(text2) {
|
|
|
33428
33407
|
}
|
|
33429
33408
|
function resolveReadinessSignal(fs12, featureDir) {
|
|
33430
33409
|
const auditFiles = [...fs12.glob(featureDir, "feature-audit.*.md")].sort(
|
|
33431
|
-
|
|
33410
|
+
compareCodePoint
|
|
33432
33411
|
);
|
|
33433
33412
|
for (let index = auditFiles.length - 1; index >= 0; index -= 1) {
|
|
33434
33413
|
const auditPath = auditFiles[index];
|
|
@@ -33447,15 +33426,6 @@ function relativeToPosix6(root, path16) {
|
|
|
33447
33426
|
}
|
|
33448
33427
|
return normalized.replace(/^\/+/u, "");
|
|
33449
33428
|
}
|
|
33450
|
-
function compareCodePoint5(left, right) {
|
|
33451
|
-
if (left < right) {
|
|
33452
|
-
return -1;
|
|
33453
|
-
}
|
|
33454
|
-
if (left > right) {
|
|
33455
|
-
return 1;
|
|
33456
|
-
}
|
|
33457
|
-
return 0;
|
|
33458
|
-
}
|
|
33459
33429
|
function escapeRegExp4(value) {
|
|
33460
33430
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
33461
33431
|
}
|
|
@@ -33485,7 +33455,7 @@ function discoverCanonicalEvidenceFiles(fs12, root, feature) {
|
|
|
33485
33455
|
}
|
|
33486
33456
|
}
|
|
33487
33457
|
}
|
|
33488
|
-
return [...discovered].sort(
|
|
33458
|
+
return [...discovered].sort(compareCodePoint);
|
|
33489
33459
|
}
|
|
33490
33460
|
function parseVerificationEvidenceMarkdown(params) {
|
|
33491
33461
|
const { feature, sourceFile, markdown } = params;
|
|
@@ -33581,15 +33551,6 @@ function relativeToPosix7(root, absolute) {
|
|
|
33581
33551
|
}
|
|
33582
33552
|
return normalized.replace(/^\/+/u, "");
|
|
33583
33553
|
}
|
|
33584
|
-
function compareCodePoint6(left, right) {
|
|
33585
|
-
if (left < right) {
|
|
33586
|
-
return -1;
|
|
33587
|
-
}
|
|
33588
|
-
if (left > right) {
|
|
33589
|
-
return 1;
|
|
33590
|
-
}
|
|
33591
|
-
return 0;
|
|
33592
|
-
}
|
|
33593
33554
|
function splitLines6(value) {
|
|
33594
33555
|
if (value === "") {
|
|
33595
33556
|
return [];
|
|
@@ -33629,7 +33590,7 @@ function gatherFeatureExcerpts2(fs12, root, changedFiles) {
|
|
|
33629
33590
|
const excerpts = [];
|
|
33630
33591
|
const baseDir = `${normalizedRoot}/docs/features/active`;
|
|
33631
33592
|
const promotedDir = `${normalizedRoot}/docs/features/potential/promoted`;
|
|
33632
|
-
for (const feature of [...features].sort(
|
|
33593
|
+
for (const feature of [...features].sort(compareCodePoint)) {
|
|
33633
33594
|
const featureDir = resolveFeatureDir2(fs12, baseDir, feature);
|
|
33634
33595
|
const promotedFeatureDir = resolveFeatureDir2(fs12, promotedDir, feature);
|
|
33635
33596
|
if (featureDir === null && promotedFeatureDir === null) {
|
|
@@ -33783,7 +33744,7 @@ function buildContextFiles(params) {
|
|
|
33783
33744
|
feature
|
|
33784
33745
|
);
|
|
33785
33746
|
return [.../* @__PURE__ */ new Set([...contextFiles, ...evidenceContextFiles])].sort(
|
|
33786
|
-
|
|
33747
|
+
compareCodePoint
|
|
33787
33748
|
);
|
|
33788
33749
|
}
|
|
33789
33750
|
|
|
@@ -34219,9 +34180,9 @@ function collectPrContext(options) {
|
|
|
34219
34180
|
referencedPrsSet,
|
|
34220
34181
|
invalidRefsSet
|
|
34221
34182
|
});
|
|
34222
|
-
const referencedIssues = [...referencedIssuesSet].sort(
|
|
34223
|
-
const referencedPrs = [...referencedPrsSet].sort(
|
|
34224
|
-
const invalidRefs = [...invalidRefsSet].sort(
|
|
34183
|
+
const referencedIssues = [...referencedIssuesSet].sort(compareCodePoint);
|
|
34184
|
+
const referencedPrs = [...referencedPrsSet].sort(compareCodePoint);
|
|
34185
|
+
const invalidRefs = [...invalidRefsSet].sort(compareCodePoint);
|
|
34225
34186
|
const authorAsserted = [];
|
|
34226
34187
|
const authorReason = "None (author has not asserted autoclose issues)";
|
|
34227
34188
|
const verified = ghAvailable ? contextResult.verifiedClosing : [];
|
|
@@ -34368,16 +34329,7 @@ function errorMessage2(exc) {
|
|
|
34368
34329
|
return exc instanceof Error ? exc.message : String(exc);
|
|
34369
34330
|
}
|
|
34370
34331
|
function sortedSet3(values) {
|
|
34371
|
-
return [...new Set(values)].sort(
|
|
34372
|
-
}
|
|
34373
|
-
function compareCodePoint7(left, right) {
|
|
34374
|
-
if (left < right) {
|
|
34375
|
-
return -1;
|
|
34376
|
-
}
|
|
34377
|
-
if (left > right) {
|
|
34378
|
-
return 1;
|
|
34379
|
-
}
|
|
34380
|
-
return 0;
|
|
34332
|
+
return [...new Set(values)].sort(compareCodePoint);
|
|
34381
34333
|
}
|
|
34382
34334
|
|
|
34383
34335
|
// ../../extensions/drm-copilot/src/lib/pr-context/collector-output.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danmoisan/drm-copilot-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.14",
|
|
4
4
|
"description": "Stdio MCP server exposing drm-copilot repo-automation tools.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"overrides": {
|
|
41
41
|
"fast-uri": "^3.1.4",
|
|
42
42
|
"hono": "^4.12.27",
|
|
43
|
-
"ip-address": "^10.2
|
|
43
|
+
"ip-address": "^10.7.2",
|
|
44
44
|
"qs": "^6.15.2",
|
|
45
45
|
"@hono/node-server": "^2.0.5"
|
|
46
46
|
},
|
|
@@ -110,7 +110,8 @@ your own initiative; withdrawal is a caller decision made through F6's remove op
|
|
|
110
110
|
Update `artifacts/orchestration/parallel-planner-state.json` after every completed step with:
|
|
111
111
|
`objective`, `parallel_slug`, `parallel_manifest_path`, `mode`, `max_concurrency`,
|
|
112
112
|
`plan_home_branch`, `items[]`, `cohorts[]`, `conflict_edges[]`, `recolor_generation`,
|
|
113
|
-
`kickoff_prompt_path`, `completed_steps`, `next_step`, and `last_updated`.
|
|
113
|
+
`kickoff_prompt_path`, `completed_steps`, `next_step`, and `last_updated`. Also record the
|
|
114
|
+
`tolerated_overlaps` list returned by Get-BlastRadiusConflictEdge (tolerated-not-validated).
|
|
114
115
|
|
|
115
116
|
Each `items[]` entry carries `issue_num`, `feature_folder`, `kind`, `state`, `complexity_band`,
|
|
116
117
|
`preparation_status`, `research_path`, `plan_path`, `preflight_status`, `branch_name`,
|
|
@@ -163,6 +164,22 @@ The facade exports `Get-PlanPaths`, `Get-BlastRadius`, `Get-BlastRadiusFromObser
|
|
|
163
164
|
`Test-BlastRadiusConflict` reads that table's optional `mergeable_paths` list and contributes no
|
|
164
165
|
`path_overlap` edge for a path matching it, while the path stays in the declared radius.
|
|
165
166
|
|
|
167
|
+
The facade also exports the scheduling entry point Get-BlastRadiusConflictEdge (with its per-pair
|
|
168
|
+
helper `Get-BlastRadiusPairDecision`), the PowerShell port of `schedule_conflict_edges` in
|
|
169
|
+
`scripts/dev_tools/_blast_radius_scheduling.py`. Build the conflict edges with one call,
|
|
170
|
+
`Get-BlastRadiusConflictEdge -Item <records carrying key, radius, band> -Config <parsed truth table> -Relation ${function:Test-BlastRadiusConflict}`,
|
|
171
|
+
rather than applying `Test-BlastRadiusConflict` to each pair by hand. The -Relation argument is
|
|
172
|
+
required: the scheduling module does not resolve the relation itself, and an omitted relation fails
|
|
173
|
+
fast. It applies the detection
|
|
174
|
+
relation to every unordered pair and then the integration-cost edge rule of
|
|
175
|
+
`.claude/rules/parallel-orchestration.md` under the table's configured `conflict_tolerance`, and
|
|
176
|
+
returns `edges` and `tolerated_overlaps`. Record the edges in `conflict_edges[]` (each entry may
|
|
177
|
+
carry the tolerated extra fields `hard`, `cost`, and `benefit`) and record every returned tolerated
|
|
178
|
+
overlap in a `tolerated_overlaps` list on the planner checkpoint; tolerated overlaps are required
|
|
179
|
+
checkpoint content, never discarded. A tolerated pair runs in the same or adjacent cohorts without a
|
|
180
|
+
barrier, and the later-merging item merges `origin/main` and re-passes CI. The configured tolerance
|
|
181
|
+
is the only sanctioned relaxation: never hand-narrow a radius to suppress an edge.
|
|
182
|
+
|
|
166
183
|
**Cohort seeding and concurrency batching — bash entry points.** The bash library is granted as
|
|
167
184
|
four entry-point-specific allowlist entries — `"Bash(bash .claude/lib/bash/compute-cohorts.sh*)"`,
|
|
168
185
|
`"Bash(bash .claude/lib/bash/compute-concurrency-batches.sh*)"`,
|
|
@@ -29,9 +29,13 @@
|
|
|
29
29
|
completion is not asserted, the write is allowed via
|
|
30
30
|
hookSpecificOutput.permissionDecision='allow' (backward compatibility).
|
|
31
31
|
|
|
32
|
-
Edit tool calls supply only old_string/new_string (a partial patch)
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
Edit tool calls supply only old_string/new_string (a partial patch). An Edit
|
|
33
|
+
call is validated by reading the checkpoint at the Edit's
|
|
34
|
+
targeted file_path through the injectable CheckpointReader seam and
|
|
35
|
+
applying the old_string -> new_string replacement in memory; the resulting
|
|
36
|
+
content is then evaluated like a Write. When the patch cannot be resolved
|
|
37
|
+
(the targeted file is missing or empty, the Edit has no old_string, or the
|
|
38
|
+
old_string is absent from the targeted content), the call is allowed. For
|
|
35
39
|
Write calls whose content is not valid JSON, the hook allows the operation
|
|
36
40
|
and defers to downstream tools to surface the error.
|
|
37
41
|
|
|
@@ -266,10 +270,11 @@ function Resolve-EditedCheckpointContent {
|
|
|
266
270
|
when the patch cannot be applied against the on-disk checkpoint.
|
|
267
271
|
.DESCRIPTION
|
|
268
272
|
Implements the read-then-validate Edit path. When the tool input carries
|
|
269
|
-
an old_string (an Edit patch), the
|
|
273
|
+
an old_string (an Edit patch), the checkpoint at the caller-supplied
|
|
274
|
+
CheckpointPath (the Edit's targeted file_path) is read through the
|
|
270
275
|
injectable CheckpointReader seam and the old_string -> new_string
|
|
271
276
|
replacement is applied in memory (no on-disk mutation). Returns $null
|
|
272
|
-
when there is no old_string, the
|
|
277
|
+
when there is no old_string, the targeted file does not exist or is empty, or the
|
|
273
278
|
old_string is not present in the on-disk content, signalling the caller
|
|
274
279
|
to allow (defer).
|
|
275
280
|
#>
|
|
@@ -281,7 +286,10 @@ function Resolve-EditedCheckpointContent {
|
|
|
281
286
|
$ToolInput,
|
|
282
287
|
|
|
283
288
|
[Parameter(Mandatory)]
|
|
284
|
-
[scriptblock] $CheckpointReader
|
|
289
|
+
[scriptblock] $CheckpointReader,
|
|
290
|
+
|
|
291
|
+
[Parameter(Mandatory)]
|
|
292
|
+
[string] $CheckpointPath
|
|
285
293
|
)
|
|
286
294
|
|
|
287
295
|
$oldString = $null
|
|
@@ -297,7 +305,7 @@ function Resolve-EditedCheckpointContent {
|
|
|
297
305
|
$newString = [string]$ToolInput.new_string
|
|
298
306
|
}
|
|
299
307
|
|
|
300
|
-
$onDisk = & $CheckpointReader
|
|
308
|
+
$onDisk = & $CheckpointReader $CheckpointPath
|
|
301
309
|
if ([string]::IsNullOrEmpty([string]$onDisk)) {
|
|
302
310
|
# The on-disk checkpoint does not exist (or is empty); cannot patch.
|
|
303
311
|
return $null
|
|
@@ -363,7 +371,7 @@ function Invoke-CompletionConsistencyDecision {
|
|
|
363
371
|
# apply the old_string -> new_string patch in memory (read-then-validate).
|
|
364
372
|
$content = Get-ClaudeHookToolInputString -ToolInput $toolInput -Name 'content'
|
|
365
373
|
if (-not $content) {
|
|
366
|
-
$content = Resolve-EditedCheckpointContent -ToolInput $toolInput -CheckpointReader $CheckpointReader
|
|
374
|
+
$content = Resolve-EditedCheckpointContent -ToolInput $toolInput -CheckpointReader $CheckpointReader -CheckpointPath $filePath
|
|
367
375
|
if (-not $content) {
|
|
368
376
|
# No content, and the Edit could not be resolved against on-disk
|
|
369
377
|
# state (missing file or non-matching patch): defer and allow.
|
|
@@ -28,12 +28,12 @@ $script:OrchestrationBookkeepingTrees = @(
|
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
# Characters that make a command line statically unresolvable (D4 row 12, as narrowed by
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# literal text, so a quoted Co-Authored-By trailer is not a redirection.
|
|
31
|
+
# issues #663 and #713): interpolation characters outside single quotes; outside-quote
|
|
32
|
+
# characters (`<`, `>`, and the `#` comment introducer) outside any quote; and typographic
|
|
33
|
+
# quotes (U+2018 to U+201E) anywhere, because a PowerShell host reads them as quotes.
|
|
35
34
|
$script:InterpolationCommandCharacters = [char[]]@('$', '`')
|
|
36
|
-
$script:
|
|
35
|
+
$script:OutsideQuoteCommandCharacters = [char[]]@('>', '<', '#')
|
|
36
|
+
$script:TypographicQuoteCharacters = [char[]]@(0x2018, 0x2019, 0x201A, 0x201B, 0x201C, 0x201D, 0x201E)
|
|
37
37
|
|
|
38
38
|
# Wildcards that make an operand a glob (D4 row 15). Only the literal prefix before the
|
|
39
39
|
# first of these is prefix-tested.
|
|
@@ -60,10 +60,9 @@ function Split-OrchestrationCommandLine {
|
|
|
60
60
|
.SYNOPSIS
|
|
61
61
|
Splits a command line into segments on chain operators outside quotes.
|
|
62
62
|
.DESCRIPTION
|
|
63
|
-
Realizes D4 row 13. Quote state
|
|
64
|
-
|
|
65
|
-
the
|
|
66
|
-
caller denies (D4 rows 11 and 13). Empty and whitespace-only segments are dropped.
|
|
63
|
+
Realizes D4 row 13. Quote state and POSIX backslash escapes (issue #710) are tracked, so a quoted or
|
|
64
|
+
escaped chain operator does not split. `Balanced` reports whether the scan ended outside every quote;
|
|
65
|
+
the caller denies unbalanced text (D4 rows 11 and 13). Empty and whitespace-only segments are dropped.
|
|
67
66
|
.OUTPUTS
|
|
68
67
|
System.Collections.Hashtable with keys `Balanced` (bool) and `Segments` (string[]).
|
|
69
68
|
#>
|
|
@@ -74,8 +73,9 @@ function Split-OrchestrationCommandLine {
|
|
|
74
73
|
$segments = [System.Collections.Generic.List[string]]::new()
|
|
75
74
|
$current = [System.Text.StringBuilder]::new()
|
|
76
75
|
$openQuote = [char]0
|
|
77
|
-
|
|
76
|
+
$escaped = $false
|
|
78
77
|
foreach ($character in $CommandText.ToCharArray()) {
|
|
78
|
+
if ($escaped -or ($character -eq '\' -and $openQuote -ne "'")) { $escaped = -not $escaped; [void]$current.Append($character); continue }
|
|
79
79
|
if ($openQuote -ne [char]0) {
|
|
80
80
|
if ($character -eq $openQuote) {
|
|
81
81
|
$openQuote = [char]0
|
|
@@ -112,12 +112,12 @@ function Test-OrchestrationCommandTextUnresolvable {
|
|
|
112
112
|
.SYNOPSIS
|
|
113
113
|
Reports whether a command line carries a statically unresolvable character.
|
|
114
114
|
.DESCRIPTION
|
|
115
|
-
Realizes D4 row 12 as narrowed by
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
Realizes D4 row 12 as narrowed by issues #663 and #713, with quote state tracked as in
|
|
116
|
+
Split-OrchestrationCommandLine. `$` or backtick answers true outside quotes or inside
|
|
117
|
+
double quotes (single quotes keep it literal); `<`, `>`, or a `#` comment answers true
|
|
118
|
+
only outside a quoted span. A typographic quote (U+2018 to U+201E), a backslash before
|
|
119
|
+
any quote character, or a backslash inside a double-quoted span answers true anywhere:
|
|
120
|
+
a shell may end the span where this scan does not (fail closed).
|
|
121
121
|
.PARAMETER CommandText
|
|
122
122
|
The full command line as the shell would receive it.
|
|
123
123
|
.OUTPUTS
|
|
@@ -127,17 +127,17 @@ function Test-OrchestrationCommandTextUnresolvable {
|
|
|
127
127
|
[OutputType([bool])]
|
|
128
128
|
param([Parameter(Mandatory)][AllowEmptyString()][string] $CommandText)
|
|
129
129
|
|
|
130
|
-
# An escaped quote moves a span boundary the scan cannot model (
|
|
131
|
-
if ($CommandText.Contains('\"') -or $CommandText.Contains("\'")) {
|
|
130
|
+
# An escaped or typographic quote moves a span boundary the scan cannot model (#663, #713).
|
|
131
|
+
if ($CommandText.Contains('\"') -or $CommandText.Contains("\'") -or $CommandText.IndexOfAny($script:TypographicQuoteCharacters) -ge 0) {
|
|
132
132
|
return $true
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
$openQuote = [char]0
|
|
136
136
|
|
|
137
137
|
# Scan every character once, tracking whether it sits inside a quoted span so that
|
|
138
|
-
#
|
|
138
|
+
# outside-quote characters are judged only where the shell would honour them.
|
|
139
139
|
foreach ($character in $CommandText.ToCharArray()) {
|
|
140
|
-
if ($script:InterpolationCommandCharacters -contains $character) {
|
|
140
|
+
if ($openQuote -ne "'" -and $script:InterpolationCommandCharacters -contains $character) {
|
|
141
141
|
return $true
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -153,7 +153,7 @@ function Test-OrchestrationCommandTextUnresolvable {
|
|
|
153
153
|
}
|
|
154
154
|
} elseif ($character -eq '"' -or $character -eq "'") {
|
|
155
155
|
$openQuote = $character
|
|
156
|
-
} elseif ($script:
|
|
156
|
+
} elseif ($script:OutsideQuoteCommandCharacters -contains $character) {
|
|
157
157
|
return $true
|
|
158
158
|
}
|
|
159
159
|
}
|
|
@@ -393,21 +393,21 @@ function Test-ExemptOrchestrationSegmentToken {
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
if ($candidate.StartsWith('-')) {
|
|
396
|
-
#
|
|
396
|
+
# Only the message and trailer options of commit are modelled (issue #713). Rows
|
|
397
397
|
# 2, 5, 6, 8, and 10 all land here and deny, including a dash-leading
|
|
398
398
|
# operand supplied without a preceding separator.
|
|
399
399
|
if ($subcommand -cne 'commit') {
|
|
400
400
|
return $false
|
|
401
401
|
}
|
|
402
|
-
if ($candidate -ceq '-m' -or $candidate -ceq '--message') {
|
|
403
|
-
# The message value is the following token and is not a pathspec.
|
|
402
|
+
if ($candidate -ceq '-m' -or $candidate -ceq '--message' -or $candidate -ceq '--trailer') {
|
|
403
|
+
# The message or trailer value is the following token and is not a pathspec.
|
|
404
404
|
$index += 2
|
|
405
405
|
if ($index -gt $Token.Count) {
|
|
406
406
|
return $false
|
|
407
407
|
}
|
|
408
408
|
continue
|
|
409
409
|
}
|
|
410
|
-
if ($candidate.StartsWith('--message=') -or
|
|
410
|
+
if ($candidate.StartsWith('--message=') -or $candidate.StartsWith('--trailer=') -or
|
|
411
411
|
($candidate.Length -gt 2 -and $candidate.StartsWith('-m'))) {
|
|
412
412
|
$index++
|
|
413
413
|
continue
|
|
@@ -464,8 +464,8 @@ function Test-ExemptOrchestrationStagingCommand {
|
|
|
464
464
|
return $false
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
-
# Row 12:
|
|
468
|
-
#
|
|
467
|
+
# Row 12: `$` or backtick outside single quotes, `<`, `>`, or `#` outside quotes, any
|
|
468
|
+
# typographic quote, and unmodelled backslash escapes make the operand list untrustworthy.
|
|
469
469
|
if (Test-OrchestrationCommandTextUnresolvable -CommandText $CommandText) {
|
|
470
470
|
return $false
|
|
471
471
|
}
|
|
@@ -18,6 +18,17 @@
|
|
|
18
18
|
- Test-BlastRadius port of validate_blast_radius
|
|
19
19
|
- Test-BlastRadiusConflict port of conflicts
|
|
20
20
|
|
|
21
|
+
It also re-exports two scheduling functions of BlastRadiusScheduling.psm1
|
|
22
|
+
(issue #722); each takes the relation as -Relation, so a caller passes
|
|
23
|
+
${function:Test-BlastRadiusConflict} from this facade:
|
|
24
|
+
|
|
25
|
+
- Get-BlastRadiusConflictEdge port of schedule_conflict_edges
|
|
26
|
+
- Get-BlastRadiusPairDecision port of decide_pair
|
|
27
|
+
|
|
28
|
+
It also re-exports the six functions of BlastRadiusWriteIntent.psm1 (issue
|
|
29
|
+
#722); Get-BlastRadius and Get-NormalizedDeclaredRadius delegate their
|
|
30
|
+
write_intent_extraction flag branch to that module.
|
|
31
|
+
|
|
21
32
|
The Python modules remain the authoritative reference implementation. This
|
|
22
33
|
module is one half of a two-language mirror; it never imports validator
|
|
23
34
|
logic. Every function is pure: no filesystem, subprocess, network, or
|
|
@@ -60,6 +71,8 @@ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConfig.psm1'
|
|
|
60
71
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusNormalization.psm1') -Force -ErrorAction Stop
|
|
61
72
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusValidation.psm1') -Force -ErrorAction Stop
|
|
62
73
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConflict.psm1') -Force -ErrorAction Stop
|
|
74
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusScheduling.psm1') -Force -ErrorAction Stop
|
|
75
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusWriteIntent.psm1') -Force -ErrorAction Stop
|
|
63
76
|
|
|
64
77
|
# Feature-folder handling. Every radius contains its own feature folder, and a
|
|
65
78
|
# caller may pass either a bare folder name or an already-qualified path.
|
|
@@ -169,10 +182,19 @@ function Get-BlastRadius {
|
|
|
169
182
|
# derived radius always passes V1 and V2 against its own plan (issue #452).
|
|
170
183
|
$rootSurface = [string[]]@(Get-ConfigRootSurface -Config $Config)
|
|
171
184
|
|
|
172
|
-
|
|
185
|
+
# Write-intent mode (issue #722) delegates to BlastRadiusWriteIntent.psm1:
|
|
186
|
+
# plan paths come from the selector Test-BlastRadius also uses and the spec
|
|
187
|
+
# contributes contracts only.
|
|
173
188
|
$entry = [System.Collections.Generic.List[string]]::new()
|
|
174
|
-
|
|
175
|
-
|
|
189
|
+
if (Test-WriteIntentExtractionEnabled -Config $Config) {
|
|
190
|
+
$entry.AddRange([string[]]@(Get-PlanPathForConfig -PlanText $PlanText -Config $Config -RootSurface $rootSurface))
|
|
191
|
+
$contract = @(Get-WriteIntentSpecContract -SpecText $SpecText)
|
|
192
|
+
} else {
|
|
193
|
+
$specLine = [string[]]@(ConvertTo-NormalizedLine -Text $SpecText)
|
|
194
|
+
$entry.AddRange([string[]]@(Get-PlanPaths -PlanText $PlanText -RootSurface $rootSurface))
|
|
195
|
+
$entry.AddRange([string[]]@(Get-PathFromLine -Line $specLine -RootSurface $rootSurface))
|
|
196
|
+
$contract = @(Get-ContractIdentifier -SpecText $SpecText)
|
|
197
|
+
}
|
|
176
198
|
|
|
177
199
|
# Read-by-mandate citations are dropped before the feature folder is added: a
|
|
178
200
|
# plan cites the policy rules because its author was told to read them, not
|
|
@@ -193,7 +215,7 @@ function Get-BlastRadius {
|
|
|
193
215
|
paths = $paths
|
|
194
216
|
modules = @(Resolve-BlastRadiusModule -PathEntry $paths -Config $Config)
|
|
195
217
|
shared_surfaces = @(Resolve-BlastRadiusSharedSurface -ConcretePath $concrete -Config $Config)
|
|
196
|
-
contracts =
|
|
218
|
+
contracts = $contract
|
|
197
219
|
source = $Source
|
|
198
220
|
computed_at = $ComputedAt
|
|
199
221
|
}
|
|
@@ -264,7 +286,14 @@ function Get-NormalizedDeclaredRadius {
|
|
|
264
286
|
}
|
|
265
287
|
}
|
|
266
288
|
|
|
267
|
-
|
|
289
|
+
# Write-intent mode (issue #722) applies the token-level rules W1, W4, and W6
|
|
290
|
+
# before the mandate-read filter; the feature-folder glob is kept.
|
|
291
|
+
$acceptedEntry = [string[]]$accepted.ToArray()
|
|
292
|
+
if (Test-WriteIntentExtractionEnabled -Config $Config) {
|
|
293
|
+
$acceptedEntry = [string[]]@(Select-WriteIntentPathEntry -Entry $acceptedEntry -RootSurface $rootSurface `
|
|
294
|
+
-PathRoot ([string[]]@(Get-ConfigPathRoot -Config $Config)))
|
|
295
|
+
}
|
|
296
|
+
$paths = [string[]]@(Get-NonMandateReadEntry -Entry $acceptedEntry `
|
|
268
297
|
-MandateRead ([string[]]@(Get-ConfigMandateRead -Config $Config)))
|
|
269
298
|
$concrete = [string[]]@(Get-ConcreteEntry -Entry $paths)
|
|
270
299
|
|
|
@@ -435,4 +464,12 @@ Export-ModuleMember -Function `
|
|
|
435
464
|
Get-NormalizedDeclaredRadius, `
|
|
436
465
|
Get-BlastRadiusFromObservedPaths, `
|
|
437
466
|
Test-BlastRadius, `
|
|
438
|
-
Test-BlastRadiusConflict
|
|
467
|
+
Test-BlastRadiusConflict, `
|
|
468
|
+
Get-BlastRadiusConflictEdge, `
|
|
469
|
+
Get-BlastRadiusPairDecision, `
|
|
470
|
+
Test-WriteIntentExtractionEnabled, `
|
|
471
|
+
Get-ConfigPathRoot, `
|
|
472
|
+
Get-WriteIntentPlanPath, `
|
|
473
|
+
Get-WriteIntentSpecContract, `
|
|
474
|
+
Select-WriteIntentPathEntry, `
|
|
475
|
+
Get-PlanPathForConfig
|