@danmoisan/drm-copilot-mcp 1.0.26 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/out/mcp-server.js +542 -42
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +17 -21
  4. package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +17 -19
  5. package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +17 -19
  6. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +18 -19
  7. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +18 -19
  8. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +54 -32
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +172 -24
  10. package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +51 -22
  11. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +56 -19
  12. package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +71 -28
  13. package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +68 -23
  14. package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +402 -0
  15. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +20 -22
  16. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +59 -14
  17. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +17 -20
  18. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1 +278 -0
  19. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +55 -271
  20. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +57 -22
  21. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +56 -19
  22. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +70 -27
  23. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +228 -0
  24. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +68 -225
  25. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +161 -34
  26. package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +59 -22
  27. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +70 -27
  28. package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +32 -18
  29. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +105 -2
  30. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +32 -52
  31. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +107 -99
  32. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +295 -0
  33. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +9 -3
  34. package/resources/claude-customizations/.claude/lib/hook-payload/HookPayload.psm1 +494 -0
  35. package/resources/claude-customizations/.claude/lib/mermaid/MermaidGrammar.psm1 +491 -0
  36. package/resources/claude-customizations/.claude/lib/mermaid/MermaidLineScanner.psm1 +488 -0
  37. package/resources/claude-customizations/.claude/lib/mermaid/MermaidMarkdownFences.psm1 +298 -0
  38. package/resources/claude-customizations/.claude/lib/mermaid/MermaidValidation.psm1 +496 -0
  39. package/resources/claude-customizations/.claude/rules/mermaid.md +142 -0
  40. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +124 -1
  41. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +116 -0
  42. package/resources/claude-customizations/.claude/settings.json +5 -0
  43. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +15 -0
  44. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  45. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +6 -0
  46. package/resources/claude-customizations/.claude/skills/mermaid-diagram/SKILL.md +184 -0
  47. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/c4.md +50 -0
  48. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/class.md +63 -0
  49. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/er.md +56 -0
  50. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/flowchart.md +68 -0
  51. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/gantt.md +51 -0
  52. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/other-types.md +82 -0
  53. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/pie.md +32 -0
  54. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/sequence.md +63 -0
  55. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/state.md +49 -0
  56. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +8 -7
  57. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +24 -4
  58. package/resources/claude-customizations/config/blast-radius.json +16 -2
  59. package/resources/claude-customizations/pack-manifests/core.json +22 -1
  60. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  61. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  62. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  63. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +31 -1
package/out/mcp-server.js CHANGED
@@ -17260,7 +17260,8 @@ function toMcpToolResult(result) {
17260
17260
  ...result.assetId === void 0 ? {} : { asset_id: result.assetId },
17261
17261
  ...result.bundledSourcePath === void 0 ? {} : { bundled_source_path: result.bundledSourcePath },
17262
17262
  ...result.destinationPath === void 0 ? {} : { destination_path: result.destinationPath },
17263
- ...result.renderedTree === void 0 ? {} : { rendered_tree: result.renderedTree }
17263
+ ...result.renderedTree === void 0 ? {} : { rendered_tree: result.renderedTree },
17264
+ ...result.warnings === void 0 ? {} : { warnings: result.warnings }
17264
17265
  };
17265
17266
  }
17266
17267
  function toFailureToolResult(tool, workspaceRoot, error2) {
@@ -19003,7 +19004,6 @@ var EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([
19003
19004
  ]);
19004
19005
  var SCAN_DEPTH_LIMIT = 3;
19005
19006
  var PAYLOAD_MODULES = {
19006
- "claude-runtime": [".claude/**"],
19007
19007
  config: ["config/**"]
19008
19008
  };
19009
19009
  var FORBIDDEN_GLOBS = [
@@ -19015,7 +19015,8 @@ var CARRIED_KEYS = [
19015
19015
  "version",
19016
19016
  "shared_surfaces",
19017
19017
  "shared_surface_globs",
19018
- "over_breadth_fraction"
19018
+ "over_breadth_fraction",
19019
+ "mandate_reads"
19019
19020
  ];
19020
19021
  var BlastRadiusDeriveError = class extends Error {
19021
19022
  /** Destination-relative path of the document that failed to parse. */
@@ -19143,6 +19144,7 @@ function deriveDestinationModuleMap(observations, sourceDocumentText) {
19143
19144
  version: source[CARRIED_KEYS[0]],
19144
19145
  shared_surfaces: source[CARRIED_KEYS[1]],
19145
19146
  shared_surface_globs: source[CARRIED_KEYS[2]],
19147
+ mandate_reads: source[CARRIED_KEYS[4]],
19146
19148
  modules,
19147
19149
  over_breadth_fraction: source[CARRIED_KEYS[3]]
19148
19150
  };
@@ -26846,6 +26848,417 @@ function validateParallelKickoffText(text) {
26846
26848
  return parseParallelKickoff(text).errors;
26847
26849
  }
26848
26850
 
26851
+ // ../../extensions/drm-copilot/src/lib/validate/plan-gate-commands.ts
26852
+ var SHELL_WHITESPACE = /* @__PURE__ */ new Set([" ", " ", "\r", "\n"]);
26853
+ var ESCAPE = "\\";
26854
+ var COV_FLAG = "--cov";
26855
+ var COV_FLAG_PREFIX = "--cov=";
26856
+ var GREP_EXECUTABLES = /* @__PURE__ */ new Set(["grep", "egrep", "fgrep", "rg"]);
26857
+ var EXECUTABLE_SCAN_LIMIT = 4;
26858
+ var PLAN_GATE_TASK_PATTERN = /^- \[(?<state>[ xX])\] \[P(?<phase>\d+)-T(?<task>\d+)\] (?<title>.+)$/;
26859
+ var PLAN_GATE_HEADING_PATTERN = /^#{1,6} /;
26860
+ var INLINE_SPAN_RE = /`([^`]+)`/g;
26861
+ var FENCE_RE = /^\s*```/;
26862
+ var MINIMUM_ARGV_LENGTH = 2;
26863
+ function splitShellWords(span) {
26864
+ const words2 = [];
26865
+ let token = null;
26866
+ let quote = null;
26867
+ let index = 0;
26868
+ while (index < span.length) {
26869
+ const char = span[index] ?? "";
26870
+ index += 1;
26871
+ if (quote === null) {
26872
+ if (SHELL_WHITESPACE.has(char)) {
26873
+ if (token !== null) {
26874
+ words2.push(token);
26875
+ token = null;
26876
+ }
26877
+ continue;
26878
+ }
26879
+ if (char === ESCAPE) {
26880
+ const next = span[index];
26881
+ if (next === void 0) {
26882
+ return null;
26883
+ }
26884
+ index += 1;
26885
+ token = (token ?? "") + next;
26886
+ continue;
26887
+ }
26888
+ if (char === '"' || char === "'") {
26889
+ quote = char;
26890
+ token = token ?? "";
26891
+ continue;
26892
+ }
26893
+ token = (token ?? "") + char;
26894
+ continue;
26895
+ }
26896
+ if (char === quote) {
26897
+ quote = null;
26898
+ continue;
26899
+ }
26900
+ if (quote === '"' && char === ESCAPE) {
26901
+ const next = span[index];
26902
+ if (next === void 0) {
26903
+ return null;
26904
+ }
26905
+ index += 1;
26906
+ if (next !== quote && next !== ESCAPE) {
26907
+ token = (token ?? "") + ESCAPE;
26908
+ }
26909
+ token = (token ?? "") + next;
26910
+ continue;
26911
+ }
26912
+ token = (token ?? "") + char;
26913
+ }
26914
+ if (quote !== null) {
26915
+ return null;
26916
+ }
26917
+ if (token !== null) {
26918
+ words2.push(token);
26919
+ }
26920
+ return words2;
26921
+ }
26922
+ function isCovFlagToken(token) {
26923
+ return token === COV_FLAG || token.startsWith(COV_FLAG_PREFIX);
26924
+ }
26925
+ function grepExecutableIndex(argv) {
26926
+ const limit = Math.min(argv.length, EXECUTABLE_SCAN_LIMIT);
26927
+ for (let index = 0; index < limit; index += 1) {
26928
+ const word = argv[index];
26929
+ if (word === void 0) {
26930
+ continue;
26931
+ }
26932
+ if (GREP_EXECUTABLES.has(word)) {
26933
+ return index;
26934
+ }
26935
+ if (word === "git" && argv[index + 1] === "grep") {
26936
+ return index + 1;
26937
+ }
26938
+ }
26939
+ return null;
26940
+ }
26941
+ function classifyKind(argv) {
26942
+ if (grepExecutableIndex(argv) !== null) {
26943
+ return "grep";
26944
+ }
26945
+ if (argv.some((word) => isCovFlagToken(word))) {
26946
+ return "pytest_cov";
26947
+ }
26948
+ return "other";
26949
+ }
26950
+ function splitLines2(text) {
26951
+ return text.split(/\r\n|\n|\r/);
26952
+ }
26953
+ function inlineSpans(line) {
26954
+ const spans = [];
26955
+ for (const match of line.matchAll(INLINE_SPAN_RE)) {
26956
+ const body = match[1];
26957
+ if (body !== void 0) {
26958
+ spans.push(body);
26959
+ }
26960
+ }
26961
+ return spans;
26962
+ }
26963
+ function appendCommand(commands, taskId, sourceLine, rawSpan) {
26964
+ const argv = splitShellWords(rawSpan);
26965
+ if (argv === null || argv.length < MINIMUM_ARGV_LENGTH) {
26966
+ return;
26967
+ }
26968
+ commands.push({
26969
+ taskId,
26970
+ sourceLine,
26971
+ rawSpan,
26972
+ argv,
26973
+ kind: classifyKind(argv)
26974
+ });
26975
+ }
26976
+ function extractPlanCommands(text) {
26977
+ const commands = [];
26978
+ let currentTask = null;
26979
+ let inFence = false;
26980
+ const lines = splitLines2(text);
26981
+ for (let index = 0; index < lines.length; index += 1) {
26982
+ const line = lines[index] ?? "";
26983
+ const lineNumber = index + 1;
26984
+ if (FENCE_RE.test(line)) {
26985
+ inFence = !inFence;
26986
+ continue;
26987
+ }
26988
+ if (inFence) {
26989
+ const fenced = line.trim();
26990
+ if (currentTask !== null && fenced !== "") {
26991
+ appendCommand(commands, currentTask, lineNumber, fenced);
26992
+ }
26993
+ continue;
26994
+ }
26995
+ if (PLAN_GATE_HEADING_PATTERN.test(line)) {
26996
+ currentTask = null;
26997
+ continue;
26998
+ }
26999
+ const taskMatch = PLAN_GATE_TASK_PATTERN.exec(line);
27000
+ if (taskMatch !== null) {
27001
+ const phase = taskMatch.groups?.["phase"] ?? "";
27002
+ const task = taskMatch.groups?.["task"] ?? "";
27003
+ currentTask = `P${phase}-T${task}`;
27004
+ }
27005
+ if (currentTask === null) {
27006
+ continue;
27007
+ }
27008
+ for (const span of inlineSpans(line)) {
27009
+ appendCommand(commands, currentTask, lineNumber, span);
27010
+ }
27011
+ }
27012
+ return commands;
27013
+ }
27014
+
27015
+ // ../../extensions/drm-copilot/src/lib/validate/plan-gate-rules.ts
27016
+ function emptyPlanGateReport() {
27017
+ return { blocking: [], warnings: [] };
27018
+ }
27019
+ var BLOCKING_CHANNEL = "blocking";
27020
+ var PLACEHOLDER_MARKERS = ["<", ">", "${", "$(", "%"];
27021
+ var PATH_SEPARATORS = ["/", "\\"];
27022
+ var PYTHON_SUFFIX = ".py";
27023
+ var PYTEST_NODE_SEPARATOR = "::";
27024
+ var REGEX_METACHARACTERS = /* @__PURE__ */ new Set([...".*[]^$\\(){}|+?"]);
27025
+ var FIXED_STRING_FLAG = "-F";
27026
+ var WINDOW_SIZE = 4;
27027
+ function isPlaceholder(value) {
27028
+ return PLACEHOLDER_MARKERS.some((marker) => value.includes(marker));
27029
+ }
27030
+ function dottedRemedy(value) {
27031
+ const stem2 = value.endsWith(PYTHON_SUFFIX) ? value.slice(0, value.length - PYTHON_SUFFIX.length) : value;
27032
+ return stem2.split("/").join(".").split("\\").join(".");
27033
+ }
27034
+ function covValues(command) {
27035
+ const values = [];
27036
+ const argv = command.argv;
27037
+ for (let index = 0; index < argv.length; index += 1) {
27038
+ const word = argv[index];
27039
+ if (word === void 0) {
27040
+ continue;
27041
+ }
27042
+ if (word === COV_FLAG) {
27043
+ const next = argv[index + 1];
27044
+ if (next !== void 0) {
27045
+ values.push({ value: next, spaceSeparated: true });
27046
+ }
27047
+ continue;
27048
+ }
27049
+ if (word.startsWith(COV_FLAG_PREFIX)) {
27050
+ values.push({
27051
+ value: word.slice(COV_FLAG_PREFIX.length),
27052
+ spaceSeparated: false
27053
+ });
27054
+ }
27055
+ }
27056
+ return values;
27057
+ }
27058
+ function evaluateCovValue(report, command, cov, context) {
27059
+ const task = command.taskId;
27060
+ if (cov.spaceSeparated) {
27061
+ report.warnings.push(
27062
+ `[${task}] --cov argument value \`${cov.value}\` is supplied space-separated; the ambiguous form can bind the following positional argument. Use the --cov=<module> form.`
27063
+ );
27064
+ }
27065
+ if (isPlaceholder(cov.value)) {
27066
+ return;
27067
+ }
27068
+ const truncated = cov.value.split(PYTEST_NODE_SEPARATOR)[0] ?? "";
27069
+ if (truncated.endsWith(PYTHON_SUFFIX)) {
27070
+ report.blocking.push(
27071
+ `[${task}] --cov argument \`${cov.value}\` names a filesystem path; coverage.py accepts only directories or importable names. Use --cov=${dottedRemedy(truncated)}.`
27072
+ );
27073
+ return;
27074
+ }
27075
+ if (!PATH_SEPARATORS.some((separator) => cov.value.includes(separator))) {
27076
+ return;
27077
+ }
27078
+ if (context === void 0) {
27079
+ return;
27080
+ }
27081
+ try {
27082
+ evaluateTrackedCovValue(report, task, cov, truncated, context);
27083
+ } catch {
27084
+ }
27085
+ }
27086
+ function evaluateTrackedCovValue(report, task, cov, truncated, context) {
27087
+ if (context.git.isTrackedFile(truncated + PYTHON_SUFFIX)) {
27088
+ report.blocking.push(
27089
+ `[${task}] --cov argument \`${cov.value}\` names a tracked module file path; coverage.py accepts only directories or importable names. Use --cov=${dottedRemedy(truncated)}.`
27090
+ );
27091
+ return;
27092
+ }
27093
+ if (context.git.isTrackedDirectory(truncated)) {
27094
+ return;
27095
+ }
27096
+ report.warnings.push(
27097
+ `[${task}] --cov argument \`${cov.value}\` contains a path separator but resolves to neither a tracked file nor a tracked directory; coverage may collect no data. Use the importable dotted form or a tracked directory.`
27098
+ );
27099
+ }
27100
+ function patternOperand(argv) {
27101
+ const executable = grepExecutableIndex(argv);
27102
+ if (executable === null) {
27103
+ return null;
27104
+ }
27105
+ for (let index = executable + 1; index < argv.length; index += 1) {
27106
+ const word = argv[index];
27107
+ if (word === void 0 || word.startsWith("-")) {
27108
+ continue;
27109
+ }
27110
+ return word;
27111
+ }
27112
+ return null;
27113
+ }
27114
+ function isCheckableLiteral(argv, pattern) {
27115
+ if (isPlaceholder(pattern)) {
27116
+ return false;
27117
+ }
27118
+ if (argv.includes(FIXED_STRING_FLAG)) {
27119
+ return true;
27120
+ }
27121
+ return ![...pattern].some((character) => REGEX_METACHARACTERS.has(character));
27122
+ }
27123
+ function normalizeWhitespace(value) {
27124
+ return value.split(/\s+/).filter((word) => word !== "").join(" ");
27125
+ }
27126
+ function planQuotesLiteral(text, literal2, excludeSpan) {
27127
+ const remainder = text.split(excludeSpan).join(" ");
27128
+ const needle = normalizeWhitespace(literal2);
27129
+ if (needle === "") {
27130
+ return false;
27131
+ }
27132
+ return normalizeWhitespace(remainder).includes(needle);
27133
+ }
27134
+ function windowJoin(lines, size = WINDOW_SIZE) {
27135
+ const dense = lines.filter((line) => line.trim() !== "").map((line) => normalizeWhitespace(line));
27136
+ const windows = [];
27137
+ for (let index = 0; index < dense.length; index += 1) {
27138
+ windows.push(dense.slice(index, index + size).join(" "));
27139
+ }
27140
+ return windows;
27141
+ }
27142
+ function hasCrossLinePresence(context, literal2) {
27143
+ const words2 = literal2.split(/\s+/).filter((word) => word !== "");
27144
+ if (words2.length < 2) {
27145
+ return false;
27146
+ }
27147
+ const needle = words2.join(" ");
27148
+ const firstWord = words2[0] ?? "";
27149
+ for (const path11 of context.git.filesContaining(firstWord)) {
27150
+ const lines = context.git.readTrackedText(path11).split(/\r\n|\n|\r/);
27151
+ if (lines.some((line) => normalizeWhitespace(line).includes(needle))) {
27152
+ continue;
27153
+ }
27154
+ if (windowJoin(lines).some((window2) => window2.includes(needle))) {
27155
+ return true;
27156
+ }
27157
+ }
27158
+ return false;
27159
+ }
27160
+
27161
+ // ../../extensions/drm-copilot/src/lib/validate/plan-gate-discrimination.ts
27162
+ var G5_SEVERITY = "warning";
27163
+ var CommandRunnerPlanGateRepository = class {
27164
+ constructor(workspaceRoot, runner) {
27165
+ this.workspaceRoot = workspaceRoot;
27166
+ this.runner = runner;
27167
+ }
27168
+ workspaceRoot;
27169
+ runner;
27170
+ /** Run one `git` invocation and return its exit code and trimmed stdout. */
27171
+ run(arguments_) {
27172
+ const result = this.runner.run(["git", ...arguments_], {
27173
+ cwd: this.workspaceRoot,
27174
+ allowError: true
27175
+ });
27176
+ return { code: result.code, stdout: result.stdout.trim() };
27177
+ }
27178
+ /** Return tracked paths carrying the literal on a single line. */
27179
+ filesContaining(literal2) {
27180
+ const result = this.run(["grep", "-F", "-l", "--", literal2]);
27181
+ if (result.code !== 0 || result.stdout === "") {
27182
+ return [];
27183
+ }
27184
+ return result.stdout.split(/\r\n|\n|\r/).map((line) => line.trim()).filter((line) => line !== "");
27185
+ }
27186
+ /** Return whether `git ls-files` lists the path itself. */
27187
+ isTrackedFile(path11) {
27188
+ const normalized = path11.split("\\").join("/");
27189
+ const result = this.run(["ls-files", "--", normalized]);
27190
+ if (result.code !== 0 || result.stdout === "") {
27191
+ return false;
27192
+ }
27193
+ return result.stdout.split(/\r\n|\n|\r/).some((line) => line.trim() === normalized);
27194
+ }
27195
+ /** Return whether entries exist beneath the path but none equals it. */
27196
+ isTrackedDirectory(path11) {
27197
+ const normalized = path11.split("\\").join("/").replace(/\/+$/, "");
27198
+ const result = this.run(["ls-files", "--", normalized]);
27199
+ if (result.code !== 0 || result.stdout === "") {
27200
+ return false;
27201
+ }
27202
+ const listed = result.stdout.split(/\r\n|\n|\r/).map((line) => line.trim()).filter((line) => line !== "");
27203
+ return listed.length > 0 && listed.every((entry) => entry !== normalized);
27204
+ }
27205
+ /** Return the committed text of the path at `HEAD`, or an empty string. */
27206
+ readTrackedText(path11) {
27207
+ const normalized = path11.split("\\").join("/");
27208
+ const result = this.run(["show", `HEAD:${normalized}`]);
27209
+ return result.code === 0 ? result.stdout : "";
27210
+ }
27211
+ };
27212
+ function evaluateLiteral(report, text, command, context) {
27213
+ const pattern = patternOperand(command.argv);
27214
+ if (pattern === null || !isCheckableLiteral(command.argv, pattern)) {
27215
+ return;
27216
+ }
27217
+ if (context.git.filesContaining(pattern).length > 0) {
27218
+ return;
27219
+ }
27220
+ if (planQuotesLiteral(text, pattern, command.rawSpan)) {
27221
+ return;
27222
+ }
27223
+ if (hasCrossLinePresence(context, pattern)) {
27224
+ report.warnings.push(
27225
+ `[${command.taskId}] search literal \`${pattern}\` is present only across adjacent lines of a tracked file and matches no single line; a line-oriented search returns zero matches. Search a shorter single-line token.`
27226
+ );
27227
+ return;
27228
+ }
27229
+ const finding = `[${command.taskId}] search literal \`${pattern}\` is absent from the tracked tree and is not quoted in the plan; the search returns zero matches whatever the executor does. Quote the exact literal the task will create, or assert a literal that exists.`;
27230
+ const channel = G5_SEVERITY === BLOCKING_CHANNEL ? report.blocking : report.warnings;
27231
+ channel.push(finding);
27232
+ }
27233
+ function evaluateLiteralRules(report, text, commands, context) {
27234
+ const literalFindings = emptyPlanGateReport();
27235
+ try {
27236
+ for (const command of commands) {
27237
+ if (command.kind !== "grep") {
27238
+ continue;
27239
+ }
27240
+ evaluateLiteral(literalFindings, text, command, context);
27241
+ }
27242
+ } catch {
27243
+ return;
27244
+ }
27245
+ report.blocking.push(...literalFindings.blocking);
27246
+ report.warnings.push(...literalFindings.warnings);
27247
+ }
27248
+ function evaluatePlanGates(text, context) {
27249
+ const report = emptyPlanGateReport();
27250
+ const commands = extractPlanCommands(text);
27251
+ for (const command of commands) {
27252
+ for (const cov of covValues(command)) {
27253
+ evaluateCovValue(report, command, cov, context);
27254
+ }
27255
+ }
27256
+ if (context !== void 0) {
27257
+ evaluateLiteralRules(report, text, commands, context);
27258
+ }
27259
+ return report;
27260
+ }
27261
+
26849
27262
  // ../../extensions/drm-copilot/src/lib/validate/parallel-state-shared.ts
26850
27263
  function words(text) {
26851
27264
  return text.split(" ");
@@ -28020,7 +28433,7 @@ var POLICY_AUDIT_REQUIRED_CHECKLIST_LABELS = [
28020
28433
  "Per-language comparison summary:"
28021
28434
  ];
28022
28435
  var POLICY_AUDIT_COMPARISON_HEADING = "### 1.2.1 Per-Language Coverage Comparison";
28023
- var PLACEHOLDER_MARKERS = [
28436
+ var PLACEHOLDER_MARKERS2 = [
28024
28437
  "[n]",
28025
28438
  "[path",
28026
28439
  "[artifact",
@@ -28046,7 +28459,7 @@ function isNaValue(value) {
28046
28459
  }
28047
28460
  function hasPlaceholderMarker(value) {
28048
28461
  const lowered = value.toLowerCase();
28049
- return PLACEHOLDER_MARKERS.some((marker) => lowered.includes(marker));
28462
+ return PLACEHOLDER_MARKERS2.some((marker) => lowered.includes(marker));
28050
28463
  }
28051
28464
  function reportsMissingTemplateResolverSuccess(text) {
28052
28465
  const lowered = text.toLowerCase();
@@ -28353,10 +28766,28 @@ function validatePlanText(text) {
28353
28766
  }
28354
28767
  return errors;
28355
28768
  }
28356
- function validateArtifact(input) {
28769
+ function buildPlanGateContext(input) {
28770
+ if (input.fs === void 0 || input.root === void 0 || input.artifactPath === void 0 || input.runner === void 0) {
28771
+ return void 0;
28772
+ }
28773
+ return {
28774
+ workspaceRoot: input.root,
28775
+ fileSystem: input.fs,
28776
+ git: new CommandRunnerPlanGateRepository(input.root, input.runner)
28777
+ };
28778
+ }
28779
+ function validateArtifactWithWarnings(input) {
28780
+ if (input.artifactType === "plan") {
28781
+ const report = evaluatePlanGates(input.text, buildPlanGateContext(input));
28782
+ return {
28783
+ errors: [...validatePlanText(input.text), ...report.blocking],
28784
+ warnings: report.warnings
28785
+ };
28786
+ }
28787
+ return { errors: dispatchValidatorErrors(input), warnings: [] };
28788
+ }
28789
+ function dispatchValidatorErrors(input) {
28357
28790
  switch (input.artifactType) {
28358
- case "plan":
28359
- return validatePlanText(input.text);
28360
28791
  case "policy-audit":
28361
28792
  return validatePolicyAuditText(input.text);
28362
28793
  case "code-review":
@@ -28419,12 +28850,13 @@ function validateArtifact(input) {
28419
28850
  }
28420
28851
 
28421
28852
  // ../../extensions/drm-copilot/src/lib/validate/validate-orchestration-service-call.ts
28853
+ var PLAN_GATE_WARNING_PREFIX = "PLAN GATE WARNING: ";
28422
28854
  function validateOrchestrationServiceCall(input) {
28423
28855
  const artifactFullPath = toPosixPath2(
28424
28856
  path9.join(input.workspaceRoot, input.artifactPath)
28425
28857
  );
28426
28858
  const text = input.fileSystem.readTextFile(artifactFullPath);
28427
- const errors = validateArtifact({
28859
+ const { errors, warnings } = validateArtifactWithWarnings({
28428
28860
  artifactType: input.artifactType,
28429
28861
  text,
28430
28862
  ...input.requireComplete === void 0 ? {} : { requireComplete: input.requireComplete },
@@ -28438,15 +28870,17 @@ function validateOrchestrationServiceCall(input) {
28438
28870
  root: input.workspaceRoot
28439
28871
  });
28440
28872
  if (errors.length > 0) {
28873
+ const warningBlock = warnings.length === 0 ? "" : "\n" + warnings.map((warning) => `${PLAN_GATE_WARNING_PREFIX}${warning}`).join("\n");
28441
28874
  throw new Error(
28442
28875
  `Validation failed for ${input.artifactType} artifact at '${input.artifactPath}':
28443
- ${errors.join("\n")}`
28876
+ ${errors.join("\n")}${warningBlock}`
28444
28877
  );
28445
28878
  }
28446
28879
  return {
28447
28880
  tool: "validate_orchestration_artifacts",
28448
28881
  workspaceRoot: input.workspaceRoot,
28449
- summary: `Validated ${input.artifactType} artifact at '${input.artifactPath}'.`
28882
+ summary: `Validated ${input.artifactType} artifact at '${input.artifactPath}'.`,
28883
+ ...warnings.length === 0 ? {} : { warnings }
28450
28884
  };
28451
28885
  }
28452
28886
 
@@ -28669,7 +29103,7 @@ function truncate(text, limit = 800) {
28669
29103
  }
28670
29104
  return rstrip3(text.slice(0, limit - 3)) + "...";
28671
29105
  }
28672
- function splitLines2(value) {
29106
+ function splitLines3(value) {
28673
29107
  if (value === "") {
28674
29108
  return [];
28675
29109
  }
@@ -28680,7 +29114,7 @@ function splitLines2(value) {
28680
29114
  return lines;
28681
29115
  }
28682
29116
  function truncateLines(text, limit) {
28683
- const lines = splitLines2(text);
29117
+ const lines = splitLines3(text);
28684
29118
  if (lines.length <= limit) {
28685
29119
  return text;
28686
29120
  }
@@ -29444,7 +29878,7 @@ function convertNumstat(numstatText) {
29444
29878
  let adds = 0;
29445
29879
  let dels = 0;
29446
29880
  const files = [];
29447
- for (const rawLine of splitLines3(numstatText)) {
29881
+ for (const rawLine of splitLines4(numstatText)) {
29448
29882
  if (!rawLine.trim()) {
29449
29883
  continue;
29450
29884
  }
@@ -29507,7 +29941,7 @@ function summarizeConventionalCommits(subjects) {
29507
29941
  }
29508
29942
  counts.set("other", 0);
29509
29943
  const typePattern = /^(feat|fix|refactor|perf|docs|test|chore|build|ci|style)(\(|!|:)/u;
29510
- for (const rawLine of splitLines3(subjects)) {
29944
+ for (const rawLine of splitLines4(subjects)) {
29511
29945
  const line = rawLine.trim();
29512
29946
  if (!line) {
29513
29947
  continue;
@@ -29562,7 +29996,7 @@ function buildIssuesToAutocloseSection(params) {
29562
29996
  function extractChangedPaths(contextText) {
29563
29997
  const paths = [];
29564
29998
  let capture = false;
29565
- for (const line of splitLines3(contextText)) {
29999
+ for (const line of splitLines4(contextText)) {
29566
30000
  if (line.startsWith("===== Changed files")) {
29567
30001
  capture = true;
29568
30002
  continue;
@@ -29608,7 +30042,7 @@ function compareCodePoint2(left, right) {
29608
30042
  }
29609
30043
  return 0;
29610
30044
  }
29611
- function splitLines3(value) {
30045
+ function splitLines4(value) {
29612
30046
  if (value === "") {
29613
30047
  return [];
29614
30048
  }
@@ -29678,7 +30112,7 @@ function buildPrContext(options) {
29678
30112
  const subjects = git.log("--pretty=%s", revRange);
29679
30113
  const authors = git.log("--format=%an <%ae>", revRange);
29680
30114
  const authorsList = sortedSet2(
29681
- splitLines4(authors).map((line) => line.trim()).filter((line) => line)
30115
+ splitLines5(authors).map((line) => line.trim()).filter((line) => line)
29682
30116
  );
29683
30117
  const nameStatus = git.diffRange(["--name-status", mergeBase, headSha]);
29684
30118
  const numstat = git.diffRange(["--numstat", mergeBase, headSha]);
@@ -29686,7 +30120,7 @@ function buildPrContext(options) {
29686
30120
  const stat = git.diffRange(["--stat", mergeBase, headSha]);
29687
30121
  const [additions, deletions, files] = convertNumstat(numstat);
29688
30122
  const extSummary = extensionSummary(files);
29689
- const mergePrs = extractMergePrNumbers(splitLines4(oneline));
30123
+ const mergePrs = extractMergePrNumbers(splitLines5(oneline));
29690
30124
  const issueCandidates = extractIssueReferences(
29691
30125
  oneline + "\n" + subjects
29692
30126
  ).filter((ref) => !mergePrs.includes(ref));
@@ -29845,7 +30279,7 @@ function compareCodePoint3(left, right) {
29845
30279
  }
29846
30280
  return 0;
29847
30281
  }
29848
- function splitLines4(value) {
30282
+ function splitLines5(value) {
29849
30283
  if (value === "") {
29850
30284
  return [];
29851
30285
  }
@@ -29871,7 +30305,7 @@ function parseSection2(markdown, heading) {
29871
30305
  }
29872
30306
  function completedPlanTasks2(markdown, limit = 10) {
29873
30307
  const tasks = [];
29874
- for (const line of splitLines2(markdown)) {
30308
+ for (const line of splitLines3(markdown)) {
29875
30309
  if (/\[x\]/iu.test(line)) {
29876
30310
  const cleaned = line.replace(/^[-*]\s*\[[xX]\]\s*/u, "").trim();
29877
30311
  tasks.push(cleaned);
@@ -29950,7 +30384,7 @@ function parsePrimaryIssueFromMetadata(params) {
29950
30384
  params.storyText,
29951
30385
  params.issueText
29952
30386
  ]) {
29953
- for (const line of splitLines2(sourceText)) {
30387
+ for (const line of splitLines3(sourceText)) {
29954
30388
  const match = pattern.exec(line);
29955
30389
  if (match) {
29956
30390
  return match[1];
@@ -30009,11 +30443,15 @@ function escapeRegExp4(value) {
30009
30443
 
30010
30444
  // ../../extensions/drm-copilot/src/lib/pr-context/verification-evidence.ts
30011
30445
  var REQUIRED_FIELDS = ["Timestamp", "Command", "EXIT_CODE"];
30446
+ var EXPECTED_EXIT_CODE_FIELD = "ExpectedExitCode";
30012
30447
  var CANONICAL_GLOBS = [
30013
30448
  "evidence/qa-gates/**/*.md",
30014
30449
  "evidence/regression-testing/**/*.md",
30015
30450
  "evidence/other/**/*.md"
30016
30451
  ];
30452
+ function normalizeResult(exitCode, expectedExitCode) {
30453
+ return exitCode === expectedExitCode ? "pass" : "fail";
30454
+ }
30017
30455
  function discoverCanonicalEvidenceFiles(fs10, root, feature) {
30018
30456
  const normalizedRoot = toPosixPath2(root).replace(/\/+$/u, "");
30019
30457
  const featureRoot = `${normalizedRoot}/docs/features/active/${feature}`;
@@ -30033,7 +30471,7 @@ function discoverCanonicalEvidenceFiles(fs10, root, feature) {
30033
30471
  function parseVerificationEvidenceMarkdown(params) {
30034
30472
  const { feature, sourceFile, markdown } = params;
30035
30473
  const parsed = /* @__PURE__ */ new Map();
30036
- for (const rawLine of splitLines5(markdown)) {
30474
+ for (const rawLine of splitLines6(markdown)) {
30037
30475
  const colonIndex = rawLine.indexOf(":");
30038
30476
  if (colonIndex === -1) {
30039
30477
  continue;
@@ -30043,10 +30481,14 @@ function parseVerificationEvidenceMarkdown(params) {
30043
30481
  if (REQUIRED_FIELDS.includes(key) && !parsed.has(key)) {
30044
30482
  parsed.set(key, value);
30045
30483
  }
30484
+ if (key === EXPECTED_EXIT_CODE_FIELD && !parsed.has(key)) {
30485
+ parsed.set(key, value);
30486
+ }
30046
30487
  }
30047
30488
  const timestamp = parsed.get("Timestamp") ?? null;
30048
30489
  const command = parsed.get("Command") ?? null;
30049
30490
  const exitCodeRaw = parsed.get("EXIT_CODE");
30491
+ const expectedExitCodeRaw = parsed.get(EXPECTED_EXIT_CODE_FIELD);
30050
30492
  if (!timestamp || !command || exitCodeRaw === void 0) {
30051
30493
  return {
30052
30494
  feature,
@@ -30054,7 +30496,8 @@ function parseVerificationEvidenceMarkdown(params) {
30054
30496
  timestamp,
30055
30497
  command,
30056
30498
  exitCode: null,
30057
- normalizedResult: "unparseable"
30499
+ normalizedResult: "unparseable",
30500
+ expectedExitCode: 0
30058
30501
  };
30059
30502
  }
30060
30503
  const exitCode = parseIntegerStrict(exitCodeRaw);
@@ -30065,17 +30508,34 @@ function parseVerificationEvidenceMarkdown(params) {
30065
30508
  timestamp,
30066
30509
  command,
30067
30510
  exitCode: null,
30068
- normalizedResult: "unparseable"
30511
+ normalizedResult: "unparseable",
30512
+ expectedExitCode: 0
30069
30513
  };
30070
30514
  }
30071
- const normalizedResult = exitCode === 0 ? "pass" : "fail";
30515
+ const expectedExitCode = expectedExitCodeRaw === void 0 ? 0 : parseIntegerStrict(expectedExitCodeRaw);
30516
+ if (expectedExitCode === null) {
30517
+ return {
30518
+ feature,
30519
+ sourceFile,
30520
+ timestamp,
30521
+ command,
30522
+ exitCode: null,
30523
+ normalizedResult: "unparseable",
30524
+ expectedExitCode: 0
30525
+ };
30526
+ }
30527
+ const normalizedResult = normalizeResult(
30528
+ exitCode,
30529
+ expectedExitCode
30530
+ );
30072
30531
  return {
30073
30532
  feature,
30074
30533
  sourceFile,
30075
30534
  timestamp,
30076
30535
  command,
30077
30536
  exitCode,
30078
- normalizedResult
30537
+ normalizedResult,
30538
+ expectedExitCode
30079
30539
  };
30080
30540
  }
30081
30541
  function parseVerificationEvidenceFile(params) {
@@ -30111,7 +30571,7 @@ function compareCodePoint5(left, right) {
30111
30571
  }
30112
30572
  return 0;
30113
30573
  }
30114
- function splitLines5(value) {
30574
+ function splitLines6(value) {
30115
30575
  if (value === "") {
30116
30576
  return [];
30117
30577
  }
@@ -30333,7 +30793,7 @@ function extractDigestBullets(body, headings, limit) {
30333
30793
  if (!sectionText) {
30334
30794
  continue;
30335
30795
  }
30336
- for (const line of splitLines2(sectionText)) {
30796
+ for (const line of splitLines3(sectionText)) {
30337
30797
  if (!line.trim()) {
30338
30798
  continue;
30339
30799
  }
@@ -30474,7 +30934,7 @@ function parseNumstatDetailed(numstatText) {
30474
30934
  let addsTotal = 0;
30475
30935
  let delsTotal = 0;
30476
30936
  const perFile = /* @__PURE__ */ new Map();
30477
- for (const rawLine of splitLines2(numstatText)) {
30937
+ for (const rawLine of splitLines3(numstatText)) {
30478
30938
  if (!rawLine.trim()) {
30479
30939
  continue;
30480
30940
  }
@@ -30493,7 +30953,7 @@ function parseNumstatDetailed(numstatText) {
30493
30953
  }
30494
30954
  function parseNameStatusMap(nameStatusText) {
30495
30955
  const mapping = /* @__PURE__ */ new Map();
30496
- for (const rawLine of splitLines2(nameStatusText)) {
30956
+ for (const rawLine of splitLines3(nameStatusText)) {
30497
30957
  if (!rawLine.trim()) {
30498
30958
  continue;
30499
30959
  }
@@ -30541,7 +31001,7 @@ function scopingDocChanges(options) {
30541
31001
  path11
30542
31002
  ]);
30543
31003
  let headingTouched = false;
30544
- for (const line of splitLines2(diffText)) {
31004
+ for (const line of splitLines3(diffText)) {
30545
31005
  if (!line.startsWith("+") || line.startsWith("+++")) {
30546
31006
  continue;
30547
31007
  }
@@ -30557,7 +31017,7 @@ function scopingDocChanges(options) {
30557
31017
  material = true;
30558
31018
  reasons.push("key section touched");
30559
31019
  }
30560
- const addedLines = splitLines2(diffText).filter((line) => line.startsWith("+") && !line.startsWith("+++")).map((line) => line.replace(/^\++/u, "").trim());
31020
+ const addedLines = splitLines3(diffText).filter((line) => line.startsWith("+") && !line.startsWith("+++")).map((line) => line.replace(/^\++/u, "").trim());
30561
31021
  if (addedLines.length > 0 && addedLines.every(
30562
31022
  (line) => !line || line.startsWith("[") || line.startsWith("http")
30563
31023
  )) {
@@ -30957,12 +31417,15 @@ function renderVerificationEvidenceSection(fs10, resolvedRoot, featureDocs) {
30957
31417
  (left, right) => left.sourceFile < right.sourceFile ? -1 : left.sourceFile > right.sourceFile ? 1 : 0
30958
31418
  );
30959
31419
  for (const record2 of sorted) {
31420
+ const expected = record2.expectedExitCode;
31421
+ const expectedRows = expected === 0 ? [] : [` - Expected EXIT_CODE: ${String(expected)}`];
30960
31422
  lines.push(
30961
31423
  `- Feature: ${record2.feature}`,
30962
31424
  ` - Source: ${record2.sourceFile}`,
30963
31425
  ` - Timestamp: ${record2.timestamp}`,
30964
31426
  ` - Command: ${record2.command}`,
30965
31427
  ` - EXIT_CODE: ${record2.exitCode}`,
31428
+ ...expectedRows,
30966
31429
  ` - Normalized result: ${record2.normalizedResult}`
30967
31430
  );
30968
31431
  }
@@ -31795,12 +32258,13 @@ var CommandRunnerGhAdapter = class {
31795
32258
  };
31796
32259
  function potentialToIssueServiceCall(input) {
31797
32260
  const ghClient = input.gh ?? new RealGhClient({ runner: new CommandRunnerGhAdapter(input.runner) });
32261
+ const fileSystem = input.fileSystem ?? new RealPotentialFileSystem();
31798
32262
  const outcome = promotePotential({
31799
32263
  potentialPath: input.potentialPath,
31800
32264
  promotionType: input.promotionType,
31801
32265
  workMode: input.workMode,
31802
32266
  workspace: input.workspaceRoot,
31803
- fs: input.fileSystem ?? new RealPotentialFileSystem(),
32267
+ fs: fileSystem,
31804
32268
  gh: ghClient,
31805
32269
  ...input.log === void 0 ? {} : { emit: input.log }
31806
32270
  });
@@ -31811,6 +32275,11 @@ ${outcome.messages.join("\n")}` : "";
31811
32275
  }
31812
32276
  const summary = `Promoted '${input.potentialPath}' as a ${input.promotionType} workflow in ${input.workMode} mode.`;
31813
32277
  const [issueUrl] = parseIssueReference(outcome.messages);
32278
+ if (outcome.destination !== void 0 && !fileSystem.exists(outcome.destination)) {
32279
+ throw new Error(
32280
+ `potential_to_issue reported a path that does not exist: ${outcome.destination}`
32281
+ );
32282
+ }
31814
32283
  return {
31815
32284
  tool: "potential_to_issue",
31816
32285
  workspaceRoot: input.workspaceRoot,
@@ -31997,12 +32466,12 @@ function validateFeatureName2(featureName) {
31997
32466
  function escapeRegExp6(value) {
31998
32467
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31999
32468
  }
32000
- function splitLines6(text) {
32469
+ function splitLines7(text) {
32001
32470
  return text.split(/\r\n|\r|\n/);
32002
32471
  }
32003
32472
  function formatChecklist(text) {
32004
32473
  const lines = [];
32005
- for (const rawLine of splitLines6(text)) {
32474
+ for (const rawLine of splitLines7(text)) {
32006
32475
  const trimmed = rawLine.trim();
32007
32476
  if (!trimmed) {
32008
32477
  continue;
@@ -32031,7 +32500,7 @@ function getSection2(content, name) {
32031
32500
  function upsertWorkModeMarker(content, mode) {
32032
32501
  const markerLine = `- Work Mode: ${mode}`;
32033
32502
  const markerPattern = /^- Work Mode:\s*(minor-audit|full-feature|full-bug|full)\s*$/;
32034
- const lines = splitLines6(content).filter((line) => !markerPattern.test(line));
32503
+ const lines = splitLines7(content).filter((line) => !markerPattern.test(line));
32035
32504
  for (let idx = 0; idx < lines.length; idx += 1) {
32036
32505
  const line = lines[idx] ?? "";
32037
32506
  if (line.replace(/^\s+/, "").startsWith("## ")) {
@@ -32723,6 +33192,7 @@ function createActiveFolder(options) {
32723
33192
  filesystem
32724
33193
  );
32725
33194
  const potentialContent = potentialFile ? filesystem.readText(potentialFile) : "";
33195
+ const retainsPotentialSource = potentialFile !== null && isPromotedPotentialSource(potentialFile, workspacePath);
32726
33196
  const selectedWorkMode = normalizeRequestedWorkMode(workMode, featureType);
32727
33197
  const [useMinorAudit, fallbackReason] = shouldUseMinorAuditMode(
32728
33198
  selectedWorkMode,
@@ -32810,7 +33280,11 @@ function createActiveFolder(options) {
32810
33280
  if (useMinorAudit) {
32811
33281
  if (potentialFile) {
32812
33282
  potentialIssuePath = joinPosix13(targetDir, "issue.md");
32813
- filesystem.move(potentialFile, potentialIssuePath);
33283
+ if (retainsPotentialSource) {
33284
+ filesystem.copyFile(potentialFile, potentialIssuePath);
33285
+ } else {
33286
+ filesystem.move(potentialFile, potentialIssuePath);
33287
+ }
32814
33288
  const movedContent = filesystem.readText(potentialIssuePath);
32815
33289
  filesystem.writeText(
32816
33290
  potentialIssuePath,
@@ -32866,17 +33340,25 @@ function createActiveFolder(options) {
32866
33340
  if (potentialFile) {
32867
33341
  if (useMinorAudit) {
32868
33342
  if (potentialIssuePath !== null) {
32869
- emit(`Moved potential file to ${potentialIssuePath}`);
33343
+ emit(
33344
+ retainsPotentialSource ? `Copied potential file to ${potentialIssuePath}` : `Moved potential file to ${potentialIssuePath}`
33345
+ );
32870
33346
  }
32871
33347
  } else {
32872
33348
  potentialIssuePath = joinPosix13(targetDir, "issue.md");
32873
- filesystem.move(potentialFile, potentialIssuePath);
33349
+ if (retainsPotentialSource) {
33350
+ filesystem.copyFile(potentialFile, potentialIssuePath);
33351
+ } else {
33352
+ filesystem.move(potentialFile, potentialIssuePath);
33353
+ }
32874
33354
  const movedContent = filesystem.readText(potentialIssuePath);
32875
33355
  filesystem.writeText(
32876
33356
  potentialIssuePath,
32877
33357
  upsertWorkModeMarker(movedContent, selectedWorkMode)
32878
33358
  );
32879
- emit(`Moved potential file to ${potentialIssuePath}`);
33359
+ emit(
33360
+ retainsPotentialSource ? `Copied potential file to ${potentialIssuePath}` : `Moved potential file to ${potentialIssuePath}`
33361
+ );
32880
33362
  }
32881
33363
  }
32882
33364
  if (potentialFile) {
@@ -32915,6 +33397,12 @@ function isRelativeTo(child, root) {
32915
33397
  const normalizedRoot = toPosixPath2(root);
32916
33398
  return normalizedChild === normalizedRoot || normalizedChild.startsWith(`${normalizedRoot}/`);
32917
33399
  }
33400
+ function isPromotedPotentialSource(potentialPath, workspacePath) {
33401
+ return isRelativeTo(
33402
+ potentialPath,
33403
+ joinPosix13(workspacePath, "docs/features/potential/promoted")
33404
+ );
33405
+ }
32918
33406
  function posixBaseName(path11) {
32919
33407
  const normalized = toPosixPath2(path11).replace(/\/+$/, "");
32920
33408
  const slash = normalized.lastIndexOf("/");
@@ -32927,7 +33415,15 @@ function posixStem2(path11) {
32927
33415
  }
32928
33416
 
32929
33417
  // ../../extensions/drm-copilot/src/lib/new-active-feature-folder/new-active-feature-folder-service-call.ts
33418
+ function requireReportedPathExists(fileSystem, path11) {
33419
+ if (!fileSystem.exists(path11)) {
33420
+ throw new Error(
33421
+ `new_active_feature_folder reported a path that does not exist: ${path11}`
33422
+ );
33423
+ }
33424
+ }
32930
33425
  function newActiveFeatureFolderServiceCall(input) {
33426
+ const fileSystem = input.fileSystem ?? new RealFolderFileSystem();
32931
33427
  const result = createActiveFolder({
32932
33428
  featureName: input.featureName,
32933
33429
  featureType: input.type,
@@ -32935,13 +33431,17 @@ function newActiveFeatureFolderServiceCall(input) {
32935
33431
  workMode: input.workMode,
32936
33432
  workspace: input.workspaceRoot,
32937
33433
  templateRoot: input.templateRoot,
32938
- fs: input.fileSystem ?? new RealFolderFileSystem(),
33434
+ fs: fileSystem,
32939
33435
  // Route the guarded gh issue fetch through the injected F1 runner.
32940
33436
  issueFetcher: (issueNumber) => defaultIssueFetcher(issueNumber, input.runner),
32941
33437
  // No-op launcher: the MCP/non-interactive path must never open an editor.
32942
33438
  codeLauncher: () => false,
32943
33439
  ...input.log === void 0 ? {} : { emit: input.log }
32944
33440
  });
33441
+ requireReportedPathExists(fileSystem, result.target);
33442
+ if (result.potentialIssuePath !== null) {
33443
+ requireReportedPathExists(fileSystem, result.potentialIssuePath);
33444
+ }
32945
33445
  return {
32946
33446
  tool: "new_active_feature_folder",
32947
33447
  workspaceRoot: input.workspaceRoot,