project-tiny-context-harness 0.8.13 → 0.8.17

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 (103) hide show
  1. package/README.md +53 -28
  2. package/assets/README.md +52 -25
  3. package/assets/README.zh-CN.md +55 -26
  4. package/assets/agents/AGENTS_CORE.md +2 -2
  5. package/assets/agents/long-task-implementation.toml +1 -1
  6. package/assets/context_templates/screen-contract.md +16 -0
  7. package/assets/skills/context_development_engineer/SKILL.md +16 -30
  8. package/assets/skills/context_development_engineer/references/engineering-design-reasoning.md +93 -0
  9. package/assets/skills/context_surface_contract/SKILL.md +13 -2
  10. package/assets/skills/context_uiux_design/SKILL.md +7 -3
  11. package/assets/skills/context_uiux_design/references/task-uiux-analysis.md +82 -0
  12. package/assets/skills/design-resource-authoring/SKILL.md +14 -8
  13. package/assets/skills/design-resource-authoring/references/downstream-handoff.md +18 -6
  14. package/assets/skills/design-resource-authoring/references/formal-selected-web-app-handoff.md +26 -8
  15. package/assets/skills/design-resource-authoring/references/implementation-feasibility.md +111 -0
  16. package/assets/skills/design-resource-authoring/references/open-design-provider.md +45 -7
  17. package/assets/skills/design-resource-authoring/references/recovery-and-writeback.md +7 -1
  18. package/assets/skills/design-resource-authoring/references/resource-selection.md +115 -3
  19. package/assets/skills/long-task-workflow/SKILL.md +9 -7
  20. package/assets/skills/long-task-workflow/agents/openai.yaml +1 -1
  21. package/assets/skills/long-task-workflow/references/authority-lifecycle.md +1 -1
  22. package/dist/commands/design-resource.js +9 -2
  23. package/dist/commands/long-task-revision.js +4 -4
  24. package/dist/index.d.ts +1 -1
  25. package/dist/lib/design-resource-handoff-bundle.js +5 -0
  26. package/dist/lib/design-resource-handoff-input-types.d.ts +1 -1
  27. package/dist/lib/design-resource-handoff-manifest-projection.js +1 -0
  28. package/dist/lib/design-resource-handoff-set-integrity.js +1 -0
  29. package/dist/lib/design-resource-handoff-shape.js +6 -2
  30. package/dist/lib/design-resource-handoff-types.d.ts +5 -0
  31. package/dist/lib/design-resource-handoff-validation.js +12 -0
  32. package/dist/lib/design-resource-implementation-feasibility-model.d.ts +35 -0
  33. package/dist/lib/design-resource-implementation-feasibility-model.js +62 -0
  34. package/dist/lib/design-resource-implementation-feasibility-shape-sections.d.ts +43 -0
  35. package/dist/lib/design-resource-implementation-feasibility-shape-sections.js +142 -0
  36. package/dist/lib/design-resource-implementation-feasibility-shape.d.ts +4 -0
  37. package/dist/lib/design-resource-implementation-feasibility-shape.js +154 -0
  38. package/dist/lib/design-resource-implementation-feasibility-source-decision-projection.d.ts +24 -0
  39. package/dist/lib/design-resource-implementation-feasibility-source-decision-projection.js +106 -0
  40. package/dist/lib/design-resource-implementation-feasibility-source-decision.d.ts +21 -0
  41. package/dist/lib/design-resource-implementation-feasibility-source-decision.js +75 -0
  42. package/dist/lib/design-resource-implementation-feasibility-types.d.ts +135 -0
  43. package/dist/lib/design-resource-implementation-feasibility-types.js +37 -0
  44. package/dist/lib/design-resource-implementation-feasibility-validation-cells.d.ts +5 -0
  45. package/dist/lib/design-resource-implementation-feasibility-validation-cells.js +125 -0
  46. package/dist/lib/design-resource-implementation-feasibility-validation-document.d.ts +4 -0
  47. package/dist/lib/design-resource-implementation-feasibility-validation-document.js +167 -0
  48. package/dist/lib/design-resource-implementation-feasibility-validation-facts.d.ts +3 -0
  49. package/dist/lib/design-resource-implementation-feasibility-validation-facts.js +76 -0
  50. package/dist/lib/design-resource-implementation-feasibility-validation-realizations.d.ts +8 -0
  51. package/dist/lib/design-resource-implementation-feasibility-validation-realizations.js +76 -0
  52. package/dist/lib/design-resource-implementation-feasibility-validation-support.d.ts +10 -0
  53. package/dist/lib/design-resource-implementation-feasibility-validation-support.js +75 -0
  54. package/dist/lib/design-resource-implementation-feasibility-validation.d.ts +3 -0
  55. package/dist/lib/design-resource-implementation-feasibility-validation.js +138 -0
  56. package/dist/lib/design-resource-symbolic-fact-shape.js +3 -1
  57. package/dist/lib/design-resource-symbolic-fact-types.d.ts +5 -0
  58. package/dist/lib/design-resource-symbolic-fact-validation.js +13 -0
  59. package/dist/lib/long-task-activation-validation.js +77 -7
  60. package/dist/lib/long-task-authority-revision-brief.js +3 -1
  61. package/dist/lib/long-task-authority-revision-summary.js +6 -2
  62. package/dist/lib/long-task-authority-revision-types.d.ts +2 -0
  63. package/dist/lib/long-task-check-runner.js +3 -296
  64. package/dist/lib/long-task-command-process.d.ts +9 -0
  65. package/dist/lib/long-task-command-process.js +175 -0
  66. package/dist/lib/long-task-delivery-validation.d.ts +5 -2
  67. package/dist/lib/long-task-delivery-validation.js +4 -4
  68. package/dist/lib/long-task-design-feasibility-binding-owners.d.ts +6 -0
  69. package/dist/lib/long-task-design-feasibility-binding-owners.js +52 -0
  70. package/dist/lib/long-task-design-feasibility-binding.d.ts +13 -0
  71. package/dist/lib/long-task-design-feasibility-binding.js +114 -0
  72. package/dist/lib/long-task-design-feasibility-source-closure.d.ts +11 -0
  73. package/dist/lib/long-task-design-feasibility-source-closure.js +78 -0
  74. package/dist/lib/long-task-design-resource-handoff.d.ts +3 -3
  75. package/dist/lib/long-task-design-resource-handoff.js +39 -3
  76. package/dist/lib/long-task-design-resource-method-binding.js +16 -4
  77. package/dist/lib/long-task-freshness.js +1 -1
  78. package/dist/lib/long-task-git.d.ts +26 -0
  79. package/dist/lib/long-task-git.js +130 -0
  80. package/dist/lib/long-task-paths.js +1 -1
  81. package/dist/lib/long-task-process-runtime-closure.js +74 -42
  82. package/dist/lib/long-task-process-table.d.ts +12 -0
  83. package/dist/lib/long-task-process-table.js +119 -0
  84. package/dist/lib/long-task-process-tree.d.ts +9 -0
  85. package/dist/lib/long-task-process-tree.js +138 -0
  86. package/dist/lib/long-task-protected-files.d.ts +1 -1
  87. package/dist/lib/long-task-protected-files.js +1 -1
  88. package/dist/lib/long-task-ui-surface-policy.d.ts +2 -1
  89. package/dist/lib/long-task-ui-surface-policy.js +7 -4
  90. package/dist/lib/long-task-ui-surface-validation.d.ts +3 -1
  91. package/dist/lib/long-task-ui-surface-validation.js +2 -2
  92. package/dist/lib/long-task-verifier-authority.js +2 -0
  93. package/dist/lib/long-task-workspace-manifest.d.ts +8 -0
  94. package/dist/lib/long-task-workspace-manifest.js +173 -0
  95. package/dist/lib/long-task-workspace-snapshot.d.ts +8 -0
  96. package/dist/lib/long-task-workspace-snapshot.js +176 -0
  97. package/dist/lib/long-task-workspace.d.ts +4 -25
  98. package/dist/lib/long-task-workspace.js +3 -432
  99. package/dist/lib/repository-path-safety.d.ts +2 -0
  100. package/dist/lib/repository-path-safety.js +36 -0
  101. package/dist/long-task-hook.js +12 -3
  102. package/dist/public-types.d.ts +1 -0
  103. package/package.json +1 -1
@@ -110,62 +110,94 @@ function selectBinding(selected, scoped) {
110
110
  function argvAttributedRepositoryFiles(argv, cwd, bindings, targetRef) {
111
111
  const result = new Set();
112
112
  for (const argument of argv) {
113
- const value = rootArgvPathValue(argument);
114
- if (!value)
113
+ const classification = classifyRootArgvToken(argument);
114
+ if (classification.kind === "label")
115
115
  continue;
116
- if (unsafeArgvPathValue(value))
117
- fail("process_root_argv_unsafe", `${targetRef}:${argument}`);
118
- const candidate = path.posix.join(cwd === "." ? "" : cwd, value);
116
+ if (classification.kind === "external_reference")
117
+ fail("process_root_argv_unsafe", `${targetRef}:${argument}:${classification.reason}`);
118
+ const candidate = path.posix.join(cwd === "." ? "" : cwd, classification.value);
119
119
  let normalized;
120
120
  try {
121
121
  normalized = normalizeRepositoryFile(candidate, "process_root_argv");
122
122
  }
123
123
  catch {
124
- fail("process_root_argv_unsafe", `${targetRef}:${argument}`);
124
+ fail("process_root_argv_unsafe", `${targetRef}:${argument}:parent_escape`);
125
125
  }
126
126
  if (bindingsContaining(bindings, normalized).length)
127
127
  result.add(normalized);
128
128
  }
129
129
  return [...result].sort();
130
130
  }
131
- function rootArgvPathValue(argument) {
132
- const parsedArgument = unwrapCompleteQuoteLayer(argument.replace(/\\/gu, "/"));
133
- const portable = parsedArgument.value;
134
- if (!portable)
135
- return null;
136
- // A compound shell command is not one finite argv file token. In
137
- // particular, do not search inside it for a path-shaped substring.
138
- if (!parsedArgument.quoted && /\s/u.test(portable))
139
- return null;
140
- // A single-letter slash switch is an argv label (for example cmd.exe /d),
141
- // not a repository path. Longer slash-prefixed values remain absolute and
142
- // fail closed below.
143
- if (/^\/[A-Za-z?]$/u.test(portable))
144
- return null;
145
- if (!portable.startsWith("--"))
146
- return portable;
147
- const separator = portable.indexOf("=");
148
- if (separator <= 2 || separator === portable.length - 1)
149
- return null;
150
- const parsedValue = unwrapCompleteQuoteLayer(portable.slice(separator + 1));
151
- if (!parsedValue.value)
152
- return null;
153
- if (!parsedValue.quoted && /\s/u.test(parsedValue.value))
154
- return null;
155
- return parsedValue.value;
131
+ function classifyRootArgvToken(argument) {
132
+ if (!argument)
133
+ return { kind: "label" };
134
+ if (hasUnsafeArgvControl(argument))
135
+ return externalArgvReference("unsupported_compound");
136
+ if (argument === "--")
137
+ return { kind: "label" };
138
+ if (argument.startsWith("--")) {
139
+ const assignment = /^--[A-Za-z0-9][A-Za-z0-9_-]*=(.*)$/u.exec(argument);
140
+ if (assignment) {
141
+ const value = assignment[1];
142
+ if (!value)
143
+ return { kind: "label" };
144
+ if (value.includes("="))
145
+ return externalArgvReference("unsupported_compound");
146
+ return classifyRootArgvValue(value);
147
+ }
148
+ if (/^--[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(argument))
149
+ return { kind: "label" };
150
+ return externalArgvReference("unsupported_compound");
151
+ }
152
+ if (/^-[A-Za-z0-9?]$/u.test(argument))
153
+ return { kind: "label" };
154
+ if (/^-\d+(?:\.\d+)?$/u.test(argument))
155
+ return { kind: "label" };
156
+ if (argument.startsWith("-") || argument.startsWith("@"))
157
+ return externalArgvReference("unsupported_compound");
158
+ if (argument.includes("="))
159
+ return externalArgvReference("unsupported_compound");
160
+ return classifyRootArgvValue(argument);
161
+ }
162
+ function classifyRootArgvValue(value) {
163
+ if (!value)
164
+ return { kind: "label" };
165
+ if (hasUnsafeArgvControl(value))
166
+ return externalArgvReference("unsupported_compound");
167
+ if (/["']/u.test(value))
168
+ return externalArgvReference("quote_ambiguous");
169
+ if (value.includes("\\"))
170
+ return externalArgvReference("platform_ambiguous");
171
+ if (/^\/[A-Za-z?]$/u.test(value))
172
+ return externalArgvReference("platform_ambiguous");
173
+ if (value.startsWith("/"))
174
+ return externalArgvReference("absolute");
175
+ if (/^[A-Za-z]:/u.test(value))
176
+ return externalArgvReference("drive_prefixed");
177
+ if (value.startsWith("@"))
178
+ return externalArgvReference("unsupported_compound");
179
+ if (hasUnsupportedArgvCompound(value))
180
+ return externalArgvReference("unsupported_compound");
181
+ if (/^node:\d+(?:\.\d+)?$/u.test(value))
182
+ return { kind: "label" };
183
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:/u.test(value))
184
+ return externalArgvReference("protocol_prefixed");
185
+ if (value.includes(":"))
186
+ return /^\d{1,4}:\d{1,4}$/u.test(value)
187
+ ? { kind: "label" }
188
+ : externalArgvReference("unsupported_compound");
189
+ return { kind: "repository_candidate", value };
190
+ }
191
+ function hasUnsafeArgvControl(value) {
192
+ return /[\u0000-\u001f\u007f]/u.test(value);
156
193
  }
157
- function unwrapCompleteQuoteLayer(value) {
158
- const quote = value[0];
159
- if ((quote === '"' || quote === "'") && value.at(-1) === quote)
160
- return { value: value.slice(1, -1), quoted: true };
161
- return { value, quoted: false };
194
+ function hasUnsupportedArgvCompound(value) {
195
+ return (value.trim() !== value ||
196
+ value.startsWith("~") ||
197
+ !/^[\p{L}\p{N}._+,: /-]+$/u.test(value));
162
198
  }
163
- function unsafeArgvPathValue(value) {
164
- return (/["']/u.test(value) ||
165
- value.startsWith("/") ||
166
- /^[A-Za-z]:\//u.test(value) ||
167
- /^file:/iu.test(value) ||
168
- /^[A-Za-z][A-Za-z0-9+.-]*:\/\//u.test(value));
199
+ function externalArgvReference(reason) {
200
+ return { kind: "external_reference", reason };
169
201
  }
170
202
  function firstMatchingPattern(file, patterns) {
171
203
  return patterns.find((pattern) => matchesRepoPattern(file, pattern)) ?? null;
@@ -0,0 +1,12 @@
1
+ export interface ProcessInstance {
2
+ pid: number;
3
+ parent_pid: number;
4
+ start_identity: string | null;
5
+ }
6
+ export declare function processInstanceMatches(expected: Pick<ProcessInstance, "pid" | "start_identity">, current: Pick<ProcessInstance, "pid" | "start_identity">): boolean;
7
+ export declare function processSnapshot(): Promise<ProcessInstance[]>;
8
+ export declare function descendantsOf(rootPid: number, snapshot: readonly ProcessInstance[]): ProcessInstance[];
9
+ export declare function liveProcessInstances(candidates: readonly ProcessInstance[], snapshot: readonly ProcessInstance[]): ProcessInstance[];
10
+ export declare function rootProcessAlive(rootPid: number, rootInstance: ProcessInstance | null, snapshot: readonly ProcessInstance[]): boolean;
11
+ export declare function uniqueProcessInstances(instances: readonly ProcessInstance[]): ProcessInstance[];
12
+ export declare function processInstanceKey(instance: ProcessInstance): string;
@@ -0,0 +1,119 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const PROCESS_TABLE_OUTPUT_LIMIT = 2 * 1024 * 1024;
4
+ const execFileAsync = promisify(execFile);
5
+ export function processInstanceMatches(expected, current) {
6
+ if (expected.pid !== current.pid)
7
+ return false;
8
+ if (expected.start_identity === null || current.start_identity === null)
9
+ return true;
10
+ return expected.start_identity === current.start_identity;
11
+ }
12
+ export async function processSnapshot() {
13
+ let stdout;
14
+ try {
15
+ const result = process.platform === "win32"
16
+ ? await execFileAsync("powershell.exe", [
17
+ "-NoLogo",
18
+ "-NoProfile",
19
+ "-NonInteractive",
20
+ "-Command",
21
+ '$rows = Get-CimInstance Win32_Process; foreach ($row in $rows) { $started = if ($null -eq $row.CreationDate) { "unknown" } else { $row.CreationDate.ToUniversalTime().Ticks }; "$($row.ProcessId) $($row.ParentProcessId) $started" }',
22
+ ], {
23
+ encoding: "utf8",
24
+ windowsHide: true,
25
+ timeout: 5_000,
26
+ maxBuffer: PROCESS_TABLE_OUTPUT_LIMIT,
27
+ })
28
+ : await execFileAsync("ps", ["-A", "-o", "pid=,ppid="], {
29
+ encoding: "utf8",
30
+ timeout: 5_000,
31
+ maxBuffer: PROCESS_TABLE_OUTPUT_LIMIT,
32
+ });
33
+ stdout = result.stdout;
34
+ }
35
+ catch (error) {
36
+ throw new Error(`process_observer_process_tree_inspection_unavailable:${message(error)}`);
37
+ }
38
+ const rows = [];
39
+ for (const line of stdout.split(/\r?\n/u)) {
40
+ const [pidText, parentText, startText] = line.trim().split(/\s+/u);
41
+ const pid = Number.parseInt(pidText ?? "", 10);
42
+ const parent = Number.parseInt(parentText ?? "", 10);
43
+ if (!Number.isSafeInteger(pid) || !Number.isSafeInteger(parent))
44
+ continue;
45
+ rows.push({
46
+ pid,
47
+ parent_pid: parent,
48
+ start_identity: process.platform === "win32" && startText ? startText : null,
49
+ });
50
+ }
51
+ return rows;
52
+ }
53
+ export function descendantsOf(rootPid, snapshot) {
54
+ const children = new Map();
55
+ for (const processInstance of snapshot) {
56
+ const row = children.get(processInstance.parent_pid);
57
+ if (row)
58
+ row.push(processInstance);
59
+ else
60
+ children.set(processInstance.parent_pid, [processInstance]);
61
+ }
62
+ const result = [];
63
+ const pending = [...(children.get(rootPid) ?? [])];
64
+ const seen = new Set();
65
+ while (pending.length) {
66
+ const processInstance = pending.shift();
67
+ const key = processInstanceKey(processInstance);
68
+ if (seen.has(key))
69
+ continue;
70
+ seen.add(key);
71
+ result.push(processInstance);
72
+ pending.push(...(children.get(processInstance.pid) ?? []));
73
+ }
74
+ return result;
75
+ }
76
+ export function liveProcessInstances(candidates, snapshot) {
77
+ if (process.platform !== "win32")
78
+ return candidates.filter((candidate) => processIdExists(candidate.pid));
79
+ const currentByPid = new Map(snapshot.map((entry) => [entry.pid, entry]));
80
+ return candidates.filter((candidate) => {
81
+ const current = currentByPid.get(candidate.pid);
82
+ return current ? processInstanceMatches(candidate, current) : false;
83
+ });
84
+ }
85
+ export function rootProcessAlive(rootPid, rootInstance, snapshot) {
86
+ if (process.platform !== "win32")
87
+ return processIdExists(rootPid);
88
+ const current = snapshot.find((entry) => entry.pid === rootPid);
89
+ if (!current)
90
+ return false;
91
+ return rootInstance ? processInstanceMatches(rootInstance, current) : true;
92
+ }
93
+ export function uniqueProcessInstances(instances) {
94
+ return [
95
+ ...new Map(instances.map((instance) => [processInstanceKey(instance), instance])).values(),
96
+ ];
97
+ }
98
+ export function processInstanceKey(instance) {
99
+ return `${instance.pid}:${instance.start_identity ?? "pid-only"}`;
100
+ }
101
+ function processIdExists(pid) {
102
+ try {
103
+ process.kill(pid, 0);
104
+ return true;
105
+ }
106
+ catch (error) {
107
+ if (nodeErrorCode(error) === "ESRCH")
108
+ return false;
109
+ throw error;
110
+ }
111
+ }
112
+ function nodeErrorCode(error) {
113
+ return error && typeof error === "object" && "code" in error
114
+ ? String(error.code)
115
+ : null;
116
+ }
117
+ function message(error) {
118
+ return error instanceof Error ? error.message : String(error);
119
+ }
@@ -0,0 +1,9 @@
1
+ export { processInstanceMatches } from "./long-task-process-table.js";
2
+ export interface ProcessTreeObserver {
3
+ stop(): void;
4
+ wait(): Promise<void>;
5
+ terminate(force: boolean): Promise<void>;
6
+ forceQuiescence(): Promise<void>;
7
+ assertQuiescent(): Promise<void>;
8
+ }
9
+ export declare function createProcessTreeObserver(rootPid: number): ProcessTreeObserver;
@@ -0,0 +1,138 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { descendantsOf, liveProcessInstances, processInstanceKey, processInstanceMatches, processSnapshot, rootProcessAlive, uniqueProcessInstances, } from "./long-task-process-table.js";
4
+ export { processInstanceMatches } from "./long-task-process-table.js";
5
+ const PROCESS_TREE_GRACE_MS = 1_000;
6
+ const execFileAsync = promisify(execFile);
7
+ export function createProcessTreeObserver(rootPid) {
8
+ let stopped = false;
9
+ let rootInstance = null;
10
+ const observed = new Map();
11
+ const monitor = observe();
12
+ return {
13
+ stop() {
14
+ stopped = true;
15
+ },
16
+ wait() {
17
+ return monitor;
18
+ },
19
+ terminate(force) {
20
+ return terminateProcessTree(rootPid, rootInstance, force, [...observed.values()], true);
21
+ },
22
+ forceQuiescence() {
23
+ return forceProcessTreeQuiescence(rootPid, rootInstance, observed);
24
+ },
25
+ assertQuiescent() {
26
+ return assertProcessTreeQuiescent(rootPid, rootInstance, observed);
27
+ },
28
+ };
29
+ async function observe() {
30
+ do {
31
+ const snapshot = await processSnapshot();
32
+ const currentRoot = snapshot.find((entry) => entry.pid === rootPid);
33
+ if (currentRoot &&
34
+ (!rootInstance || processInstanceMatches(rootInstance, currentRoot))) {
35
+ rootInstance ??= currentRoot;
36
+ for (const descendant of descendantsOf(rootPid, snapshot))
37
+ observed.set(processInstanceKey(descendant), descendant);
38
+ }
39
+ if (stopped)
40
+ return;
41
+ await delay(process.platform === "win32" ? 100 : 25);
42
+ } while (!stopped);
43
+ }
44
+ }
45
+ async function forceProcessTreeQuiescence(rootPid, rootInstance, observed) {
46
+ await terminateProcessTree(rootPid, rootInstance, true, [...observed.values()], true);
47
+ const deadline = Date.now() + PROCESS_TREE_GRACE_MS;
48
+ while (Date.now() < deadline) {
49
+ const snapshot = await processSnapshot();
50
+ const rootAlive = rootProcessAlive(rootPid, rootInstance, snapshot);
51
+ const descendantsAlive = liveProcessInstances([...observed.values()], snapshot).length;
52
+ const groupAlive = process.platform !== "win32" && processGroupExists(rootPid);
53
+ if (!rootAlive && descendantsAlive === 0 && !groupAlive)
54
+ return;
55
+ await delay(process.platform === "win32" ? 100 : 25);
56
+ }
57
+ throw new Error("process_observer_descendant_process_alive");
58
+ }
59
+ async function assertProcessTreeQuiescent(rootPid, rootInstance, observed) {
60
+ const snapshot = await processSnapshot();
61
+ const currentRoot = snapshot.find((entry) => entry.pid === rootPid);
62
+ const finalDescendants = currentRoot &&
63
+ (!rootInstance || processInstanceMatches(rootInstance, currentRoot))
64
+ ? descendantsOf(rootPid, snapshot)
65
+ : [];
66
+ const candidates = uniqueProcessInstances([
67
+ ...observed.values(),
68
+ ...finalDescendants,
69
+ ]);
70
+ const descendants = liveProcessInstances(candidates, snapshot);
71
+ const processGroupAlive = process.platform !== "win32" && processGroupExists(rootPid);
72
+ if (!descendants.length && !processGroupAlive)
73
+ return;
74
+ await terminateProcessTree(rootPid, rootInstance, true, descendants, false);
75
+ throw new Error("process_observer_descendant_process_alive");
76
+ }
77
+ async function terminateProcessTree(rootPid, rootInstance, force, knownDescendants, includeRoot) {
78
+ if (!Number.isSafeInteger(rootPid) || rootPid <= 0)
79
+ return;
80
+ if (process.platform === "win32") {
81
+ const snapshot = await processSnapshot();
82
+ const currentRoot = snapshot.find((entry) => entry.pid === rootPid);
83
+ const rootTarget = includeRoot &&
84
+ currentRoot &&
85
+ (!rootInstance || processInstanceMatches(rootInstance, currentRoot))
86
+ ? currentRoot
87
+ : null;
88
+ const currentDescendants = rootTarget
89
+ ? descendantsOf(rootPid, snapshot)
90
+ : [];
91
+ const descendants = liveProcessInstances(uniqueProcessInstances([...knownDescendants, ...currentDescendants]), snapshot).reverse();
92
+ const targets = rootTarget ? [...descendants, rootTarget] : descendants;
93
+ for (const target of targets)
94
+ await execFileAsync("taskkill.exe", ["/PID", String(target.pid), "/T", "/F"], {
95
+ windowsHide: true,
96
+ timeout: PROCESS_TREE_GRACE_MS,
97
+ }).catch(() => undefined);
98
+ return;
99
+ }
100
+ const signal = force ? "SIGKILL" : "SIGTERM";
101
+ if (includeRoot)
102
+ try {
103
+ process.kill(-rootPid, signal);
104
+ }
105
+ catch (error) {
106
+ if (nodeErrorCode(error) !== "ESRCH")
107
+ throw error;
108
+ }
109
+ const snapshot = await processSnapshot();
110
+ const descendants = liveProcessInstances(knownDescendants, snapshot);
111
+ for (const descendant of descendants.reverse())
112
+ try {
113
+ process.kill(descendant.pid, signal);
114
+ }
115
+ catch (error) {
116
+ if (nodeErrorCode(error) !== "ESRCH")
117
+ throw error;
118
+ }
119
+ }
120
+ function processGroupExists(rootPid) {
121
+ try {
122
+ process.kill(-rootPid, 0);
123
+ return true;
124
+ }
125
+ catch (error) {
126
+ if (nodeErrorCode(error) === "ESRCH")
127
+ return false;
128
+ throw error;
129
+ }
130
+ }
131
+ function nodeErrorCode(error) {
132
+ return error && typeof error === "object" && "code" in error
133
+ ? String(error.code)
134
+ : null;
135
+ }
136
+ function delay(milliseconds) {
137
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
138
+ }
@@ -1 +1 @@
1
- export { assertProtectedRepositoryFile } from "./repository-path-safety.js";
1
+ export { assertProtectedRepositoryDirectory, assertProtectedRepositoryFile, } from "./repository-path-safety.js";
@@ -1 +1 @@
1
- export { assertProtectedRepositoryFile } from "./repository-path-safety.js";
1
+ export { assertProtectedRepositoryDirectory, assertProtectedRepositoryFile, } from "./repository-path-safety.js";
@@ -1,4 +1,5 @@
1
1
  import type { CompiledClaimsV2 } from "./long-task-claims.js";
2
2
  import type { DeliveryContractV2 } from "./long-task-delivery-types.js";
3
3
  import { type Reporter } from "./long-task-ui-surface-validation.js";
4
- export declare function validateUiSurfaceBindings(contract: DeliveryContractV2, claims: CompiledClaimsV2, report?: Reporter): void;
4
+ import type { DeliveryContractStructureValidationOptions } from "./long-task-delivery-validation.js";
5
+ export declare function validateUiSurfaceBindings(contract: DeliveryContractV2, claims: CompiledClaimsV2, report?: Reporter, options?: DeliveryContractStructureValidationOptions): void;
@@ -1,6 +1,6 @@
1
1
  import { validateUiDesignBinding } from "./long-task-ui-design-policy.js";
2
2
  import { bindingCarriers, controlClaims, expectedControlProofSurface, issue, unique, validateCarrierInputs, validateRootJourney, validateTargetLocalClaimProof, } from "./long-task-ui-surface-validation.js";
3
- export function validateUiSurfaceBindings(contract, claims, report) {
3
+ export function validateUiSurfaceBindings(contract, claims, report, options = {}) {
4
4
  const targets = new Map(contract.task.execution_targets.map((target) => [target.key, target]));
5
5
  const requiredTargets = new Set(contract.task.target_profile.required_target_refs);
6
6
  const confirmations = new Map(contract.global.acceptance.external_confirmations.map((item) => [
@@ -8,9 +8,9 @@ export function validateUiSurfaceBindings(contract, claims, report) {
8
8
  item,
9
9
  ]));
10
10
  for (const outcome of contract.outcomes)
11
- validateOutcomeBindings(outcome, claims, targets, requiredTargets, confirmations, report);
11
+ validateOutcomeBindings(outcome, claims, targets, requiredTargets, confirmations, report, options);
12
12
  }
13
- function validateOutcomeBindings(outcome, claims, targets, requiredTargets, confirmations, report) {
13
+ function validateOutcomeBindings(outcome, claims, targets, requiredTargets, confirmations, report, options = {}) {
14
14
  const surfaceBindings = outcome.product.surface_bindings ?? [];
15
15
  if (outcome.product.controls.length && !surfaceBindings.length)
16
16
  issue(report, "ui_surface_binding_required", outcome.key);
@@ -39,7 +39,10 @@ function validateOutcomeBindings(outcome, claims, targets, requiredTargets, conf
39
39
  if (!requiredTargets.has(binding.target_ref))
40
40
  issue(report, "ui_surface_binding_required_target_required", `${label}:${binding.target_ref}`);
41
41
  validateBoundControls(binding, controls, boundControls, boundControlTargets, label, report);
42
- const carriers = bindingCarriers(binding.route_binding_ref, binding.component_binding_refs, technicalBindings, label, report);
42
+ const carriers = bindingCarriers(binding.route_binding_ref, binding.component_binding_refs, technicalBindings, label, report, {
43
+ allowEmptyComponentRefs: options.allowDeferredDesignComponentBindingClosure === true &&
44
+ binding.design_targets.length > 0,
45
+ });
43
46
  const rootCheck = checks.get(binding.root_journey_check_ref);
44
47
  if (!rootCheck)
45
48
  issue(report, "ui_surface_binding_root_check_unknown", `${label}:${binding.root_journey_check_ref}`);
@@ -1,7 +1,9 @@
1
1
  import type { CompiledClaimsV2 } from "./long-task-claims.js";
2
2
  import type { DeliveryCheckV2, DeliveryOutcomeV2, ProductClaimV2, ProofSurface } from "./long-task-delivery-types.js";
3
3
  export type Reporter = (message: string) => void;
4
- export declare function bindingCarriers(routeRef: string, componentRefs: string[], bindings: Map<string, DeliveryOutcomeV2["technical"]["bindings"][number]>, label: string, report?: Reporter): string[];
4
+ export declare function bindingCarriers(routeRef: string, componentRefs: string[], bindings: Map<string, DeliveryOutcomeV2["technical"]["bindings"][number]>, label: string, report?: Reporter, options?: {
5
+ allowEmptyComponentRefs?: boolean;
6
+ }): string[];
5
7
  export declare function validateRootJourney(binding: DeliveryOutcomeV2["product"]["surface_bindings"][number], check: DeliveryCheckV2, label: string, report?: Reporter): void;
6
8
  export declare function validateTargetLocalClaimProof(outcome: DeliveryOutcomeV2, claim: ProductClaimV2, targetRef: string, expectedSurface: ProofSurface | null, claims: CompiledClaimsV2, checks: Map<string, DeliveryCheckV2>, label: string, report?: Reporter): void;
7
9
  export declare function expectedControlProofSurface(runtimeFamily: string | undefined): ProofSurface;
@@ -1,6 +1,6 @@
1
1
  import { proveRepositoryPatternSubset } from "./long-task-paths.js";
2
- export function bindingCarriers(routeRef, componentRefs, bindings, label, report) {
3
- if (!componentRefs.length)
2
+ export function bindingCarriers(routeRef, componentRefs, bindings, label, report, options = {}) {
3
+ if (!componentRefs.length && !options.allowEmptyComponentRefs)
4
4
  issue(report, "ui_surface_binding_component_ref_required", label);
5
5
  unique(componentRefs, "ui_surface_binding_component_ref_duplicate", label, report);
6
6
  const carriers = [];
@@ -37,6 +37,8 @@ function changedVerifierFiles(previous, next) {
37
37
  ...Object.keys(next.bundle_files),
38
38
  ]);
39
39
  const changed = [...files].filter((file) => previous.bundle_files[file] !== next.bundle_files[file]);
40
+ if (previous.bundle_sha256 !== next.bundle_sha256)
41
+ changed.push("<bundle>");
40
42
  if (previous.schema_sha256 !== next.schema_sha256)
41
43
  changed.push("<schema>");
42
44
  if (previous.hook_sha256 !== next.hook_sha256)
@@ -0,0 +1,8 @@
1
+ import type { WorkspaceFingerprintV2, WorkspaceManifestV2 } from "./long-task-delivery-types.js";
2
+ export declare function captureWorkspaceFingerprint(rootInput: string, excludedPrefixes?: string[]): Promise<WorkspaceFingerprintV2>;
3
+ export declare function captureWorkspaceManifest(rootInput: string, workdirInput: string, _copyRoot?: string, additionalExcludedWorkdirs?: string[]): Promise<WorkspaceManifestV2>;
4
+ export declare function changedWorkspacePaths(baseline: WorkspaceManifestV2, current: WorkspaceManifestV2): string[];
5
+ export declare function changedWorkspacePathsFromHead(rootInput: string, workdirInput: string, additionalExcludedWorkdirs?: string[]): Promise<string[]>;
6
+ export declare function workspaceFingerprintExcludedPrefixes(root: string, workdirs: string[]): string[];
7
+ export declare function workspaceSnapshotExcludedPrefixes(root: string, workdirs: string[]): string[];
8
+ export declare function workspacePathExcluded(relative: string, excluded: string[]): boolean;