@yycholla/pi-dynamic-workflows 3.3.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 (179) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +325 -0
  3. package/assets/readme/hero.png +0 -0
  4. package/assets/readme/hero.svg +70 -0
  5. package/assets/readme/package-cover.png +0 -0
  6. package/assets/readme/package-cover.svg +66 -0
  7. package/assets/readme/workflow.png +0 -0
  8. package/assets/readme/workflow.svg +77 -0
  9. package/dist/accept-workflow-guidance.d.ts +14 -0
  10. package/dist/accept-workflow-guidance.js +53 -0
  11. package/dist/adversarial-review.d.ts +30 -0
  12. package/dist/adversarial-review.js +107 -0
  13. package/dist/agent-history.d.ts +20 -0
  14. package/dist/agent-history.js +122 -0
  15. package/dist/agent-registry.d.ts +83 -0
  16. package/dist/agent-registry.js +190 -0
  17. package/dist/agent.d.ts +364 -0
  18. package/dist/agent.js +714 -0
  19. package/dist/builtin-commands.d.ts +19 -0
  20. package/dist/builtin-commands.js +251 -0
  21. package/dist/builtin-workflows.d.ts +45 -0
  22. package/dist/builtin-workflows.js +121 -0
  23. package/dist/code-review.d.ts +26 -0
  24. package/dist/code-review.js +181 -0
  25. package/dist/config.d.ts +37 -0
  26. package/dist/config.js +44 -0
  27. package/dist/deep-research.d.ts +30 -0
  28. package/dist/deep-research.js +124 -0
  29. package/dist/display.d.ts +134 -0
  30. package/dist/display.js +248 -0
  31. package/dist/effort-command.d.ts +28 -0
  32. package/dist/effort-command.js +68 -0
  33. package/dist/enums.d.ts +69 -0
  34. package/dist/enums.js +78 -0
  35. package/dist/errors.d.ts +113 -0
  36. package/dist/errors.js +140 -0
  37. package/dist/extension-reload.d.ts +37 -0
  38. package/dist/extension-reload.js +78 -0
  39. package/dist/fs-persistence.d.ts +63 -0
  40. package/dist/fs-persistence.js +102 -0
  41. package/dist/index.d.ts +57 -0
  42. package/dist/index.js +35 -0
  43. package/dist/logger.d.ts +21 -0
  44. package/dist/logger.js +66 -0
  45. package/dist/model-routing.d.ts +30 -0
  46. package/dist/model-routing.js +50 -0
  47. package/dist/model-spec.d.ts +29 -0
  48. package/dist/model-spec.js +252 -0
  49. package/dist/model-tier-config.d.ts +133 -0
  50. package/dist/model-tier-config.js +249 -0
  51. package/dist/run-persistence.d.ts +180 -0
  52. package/dist/run-persistence.js +294 -0
  53. package/dist/saved-commands.d.ts +28 -0
  54. package/dist/saved-commands.js +100 -0
  55. package/dist/shared-store.d.ts +98 -0
  56. package/dist/shared-store.js +212 -0
  57. package/dist/structured-output.d.ts +19 -0
  58. package/dist/structured-output.js +30 -0
  59. package/dist/task-panel.d.ts +61 -0
  60. package/dist/task-panel.js +422 -0
  61. package/dist/usage-limit-scheduler.d.ts +145 -0
  62. package/dist/usage-limit-scheduler.js +368 -0
  63. package/dist/web-tools.d.ts +20 -0
  64. package/dist/web-tools.js +120 -0
  65. package/dist/workflow-authoring-coverage.d.ts +70 -0
  66. package/dist/workflow-authoring-coverage.js +421 -0
  67. package/dist/workflow-authoring-reference.d.ts +20 -0
  68. package/dist/workflow-authoring-reference.js +156 -0
  69. package/dist/workflow-capability-contract.d.ts +131 -0
  70. package/dist/workflow-capability-contract.js +604 -0
  71. package/dist/workflow-commands.d.ts +18 -0
  72. package/dist/workflow-commands.js +260 -0
  73. package/dist/workflow-comprehension.d.ts +133 -0
  74. package/dist/workflow-comprehension.js +1321 -0
  75. package/dist/workflow-context-measurement.d.ts +72 -0
  76. package/dist/workflow-context-measurement.js +213 -0
  77. package/dist/workflow-control-tool.d.ts +30 -0
  78. package/dist/workflow-control-tool.js +176 -0
  79. package/dist/workflow-delivery-choice.d.ts +20 -0
  80. package/dist/workflow-delivery-choice.js +48 -0
  81. package/dist/workflow-editor.d.ts +93 -0
  82. package/dist/workflow-editor.js +363 -0
  83. package/dist/workflow-manager.d.ts +492 -0
  84. package/dist/workflow-manager.js +1124 -0
  85. package/dist/workflow-paths.d.ts +22 -0
  86. package/dist/workflow-paths.js +46 -0
  87. package/dist/workflow-release-gate.d.ts +39 -0
  88. package/dist/workflow-release-gate.js +309 -0
  89. package/dist/workflow-saved.d.ts +38 -0
  90. package/dist/workflow-saved.js +126 -0
  91. package/dist/workflow-settings.d.ts +70 -0
  92. package/dist/workflow-settings.js +131 -0
  93. package/dist/workflow-tool.d.ts +71 -0
  94. package/dist/workflow-tool.js +367 -0
  95. package/dist/workflow-ui.d.ts +182 -0
  96. package/dist/workflow-ui.js +1587 -0
  97. package/dist/workflow.d.ts +333 -0
  98. package/dist/workflow.js +1151 -0
  99. package/dist/workflows-models-command.d.ts +31 -0
  100. package/dist/workflows-models-command.js +156 -0
  101. package/dist/worktree.d.ts +25 -0
  102. package/dist/worktree.js +61 -0
  103. package/extensions/workflow.ts +151 -0
  104. package/package.json +104 -0
  105. package/skills/workflow-authoring/SKILL.md +30 -0
  106. package/skills/workflow-authoring/examples/adversarial-verification.js +63 -0
  107. package/skills/workflow-authoring/examples/bounded-semantic-retry.js +56 -0
  108. package/skills/workflow-authoring/examples/classify-and-act.js +67 -0
  109. package/skills/workflow-authoring/examples/fan-out-and-synthesize.js +47 -0
  110. package/skills/workflow-authoring/examples/generate-and-filter.js +68 -0
  111. package/skills/workflow-authoring/examples/loop-until-done.js +68 -0
  112. package/skills/workflow-authoring/examples/phased-budgets.js +75 -0
  113. package/skills/workflow-authoring/examples/saved-nested-workflows.js +44 -0
  114. package/skills/workflow-authoring/examples/structured-output.js +37 -0
  115. package/skills/workflow-authoring/examples/tournament.js +81 -0
  116. package/skills/workflow-authoring/examples/validated-gate.js +63 -0
  117. package/skills/workflow-authoring/references/capabilities.md +41 -0
  118. package/skills/workflow-authoring/references/capability-details.md +357 -0
  119. package/skills/workflow-authoring/references/common-helpers.md +4 -0
  120. package/skills/workflow-authoring/references/debugging.md +27 -0
  121. package/skills/workflow-authoring/references/focused-recipes.md +11 -0
  122. package/skills/workflow-authoring/references/helpers.md +9 -0
  123. package/skills/workflow-authoring/references/lifecycle.md +33 -0
  124. package/skills/workflow-authoring/references/pattern-selection.md +15 -0
  125. package/skills/workflow-authoring/references/quality-helpers.md +8 -0
  126. package/skills/workflow-authoring/references/registry-ownership.md +15 -0
  127. package/skills/workflow-authoring/references/retry-helper.md +5 -0
  128. package/skills/workflow-authoring/references/review.md +43 -0
  129. package/skills/workflow-authoring/references/runtime.md +27 -0
  130. package/skills/workflow-authoring/references/specialized-helpers.md +19 -0
  131. package/skills/workflow-authoring/references/versions.md +13 -0
  132. package/skills/workflow-patterns/SKILL.md +51 -0
  133. package/src/accept-workflow-guidance.ts +71 -0
  134. package/src/adversarial-review.ts +120 -0
  135. package/src/agent-history.ts +157 -0
  136. package/src/agent-registry.ts +221 -0
  137. package/src/agent.ts +929 -0
  138. package/src/builtin-commands.ts +286 -0
  139. package/src/builtin-workflows.ts +155 -0
  140. package/src/code-review.ts +183 -0
  141. package/src/config.ts +55 -0
  142. package/src/deep-research.ts +135 -0
  143. package/src/display.ts +367 -0
  144. package/src/effort-command.ts +87 -0
  145. package/src/enums.ts +77 -0
  146. package/src/errors.ts +199 -0
  147. package/src/extension-reload.ts +100 -0
  148. package/src/fs-persistence.ts +124 -0
  149. package/src/index.ts +176 -0
  150. package/src/logger.ts +88 -0
  151. package/src/model-routing.ts +73 -0
  152. package/src/model-spec.ts +309 -0
  153. package/src/model-tier-config.ts +296 -0
  154. package/src/run-persistence.ts +484 -0
  155. package/src/saved-commands.ts +115 -0
  156. package/src/shared-store.ts +228 -0
  157. package/src/structured-output.ts +47 -0
  158. package/src/task-panel.ts +490 -0
  159. package/src/usage-limit-scheduler.ts +432 -0
  160. package/src/web-tools.ts +124 -0
  161. package/src/workflow-authoring-coverage.ts +486 -0
  162. package/src/workflow-authoring-reference.ts +186 -0
  163. package/src/workflow-capability-contract.ts +806 -0
  164. package/src/workflow-commands.ts +287 -0
  165. package/src/workflow-comprehension.ts +1673 -0
  166. package/src/workflow-context-measurement.ts +262 -0
  167. package/src/workflow-control-tool.ts +238 -0
  168. package/src/workflow-delivery-choice.ts +69 -0
  169. package/src/workflow-editor.ts +444 -0
  170. package/src/workflow-manager.ts +1405 -0
  171. package/src/workflow-paths.ts +63 -0
  172. package/src/workflow-release-gate.ts +529 -0
  173. package/src/workflow-saved.ts +180 -0
  174. package/src/workflow-settings.ts +194 -0
  175. package/src/workflow-tool.ts +464 -0
  176. package/src/workflow-ui.ts +1789 -0
  177. package/src/workflow.ts +1615 -0
  178. package/src/workflows-models-command.ts +211 -0
  179. package/src/worktree.ts +76 -0
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Filesystem layout for pi-dynamic-workflows state.
3
+ *
4
+ * New writes live under the user's workflow home so projects do not get
5
+ * scattered `.pi/workflows` directories. Project-scoped state is still isolated
6
+ * by a stable cwd-derived namespace.
7
+ */
8
+
9
+ import { createHash } from "node:crypto";
10
+ import { homedir } from "node:os";
11
+ import { basename, join, resolve } from "node:path";
12
+ import { WORKFLOW_RUNS_DIR, WORKFLOW_SAVED_DIR } from "./config.js";
13
+
14
+ export const WORKFLOW_HOME_RELATIVE_DIR = ".pi/workflows";
15
+ export const WORKFLOW_PROJECTS_SUBDIR = "projects";
16
+
17
+ export interface WorkflowProjectPaths {
18
+ key: string;
19
+ rootDir: string;
20
+ runsDir: string;
21
+ savedDir: string;
22
+ settingsPath: string;
23
+ legacyRunsDir: string;
24
+ legacySavedDir: string;
25
+ }
26
+
27
+ export function workflowHomeDir(): string {
28
+ return join(homedir(), WORKFLOW_HOME_RELATIVE_DIR);
29
+ }
30
+
31
+ export function workflowUserSavedDir(): string {
32
+ return join(workflowHomeDir(), "saved");
33
+ }
34
+
35
+ export function workflowProjectKey(cwd: string): string {
36
+ const projectPath = resolve(cwd);
37
+ const slug = sanitizePathSegment(basename(projectPath) || "project");
38
+ const hash = createHash("sha256").update(projectPath).digest("hex").slice(0, 12);
39
+ return `${slug}-${hash}`;
40
+ }
41
+
42
+ export function workflowProjectPaths(cwd: string): WorkflowProjectPaths {
43
+ const key = workflowProjectKey(cwd);
44
+ const rootDir = join(workflowHomeDir(), WORKFLOW_PROJECTS_SUBDIR, key);
45
+ return {
46
+ key,
47
+ rootDir,
48
+ runsDir: join(rootDir, "runs"),
49
+ savedDir: join(rootDir, "saved"),
50
+ settingsPath: join(rootDir, "settings.json"),
51
+ legacyRunsDir: resolve(cwd, WORKFLOW_RUNS_DIR),
52
+ legacySavedDir: resolve(cwd, WORKFLOW_SAVED_DIR),
53
+ };
54
+ }
55
+
56
+ function sanitizePathSegment(value: string): string {
57
+ const sanitized = value
58
+ .toLowerCase()
59
+ .replace(/[^a-z0-9._-]+/g, "-")
60
+ .replace(/^-+|-+$/g, "")
61
+ .slice(0, 48);
62
+ return sanitized || "project";
63
+ }
@@ -0,0 +1,529 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, join, normalize, relative } from "node:path";
4
+ import packageJson from "../package.json" with { type: "json" };
5
+ import {
6
+ CapabilityClassification,
7
+ CapabilitySupport,
8
+ DiscoveryPlacement,
9
+ WorkflowAuthoringProtection,
10
+ WorkflowReleaseDiagnosticCode,
11
+ } from "./enums.js";
12
+ import {
13
+ WORKFLOW_AUTHORING_COVERAGE,
14
+ WORKFLOW_AUTHORING_FROZEN_FILES,
15
+ WORKFLOW_AUTHORING_PATTERN_IDS,
16
+ WORKFLOW_AUTHORING_RECIPE_IDS,
17
+ WORKFLOW_COMPREHENSION_SCENARIO_IDS,
18
+ type WorkflowAuthoringCoverageEntry,
19
+ } from "./workflow-authoring-coverage.js";
20
+ import {
21
+ type CAPABILITY_PUBLICATION_PATHS,
22
+ checkWorkflowCapabilityPublications,
23
+ } from "./workflow-authoring-reference.js";
24
+ import { WORKFLOW_CAPABILITY_DEFINITION, type WorkflowCapabilityDefinition } from "./workflow-capability-contract.js";
25
+ import { checkWorkflowContextMeasurement, WORKFLOW_CONTEXT_MEASUREMENT_PATH } from "./workflow-context-measurement.js";
26
+ import { createWorkflowTool } from "./workflow-tool.js";
27
+
28
+ /** Re-exported stable diagnostic codes for release automation. */
29
+ export { WorkflowReleaseDiagnosticCode } from "./enums.js";
30
+
31
+ /** One actionable release alignment error or warning. */
32
+ export interface WorkflowReleaseDiagnostic {
33
+ code: WorkflowReleaseDiagnosticCode;
34
+ severity: "error" | "warning";
35
+ subject: string;
36
+ message: string;
37
+ }
38
+
39
+ type PublicationPath = (typeof CAPABILITY_PUBLICATION_PATHS)[number];
40
+
41
+ /** Inputs and test overrides for the model-free workflow release gate. */
42
+ export interface WorkflowReleaseCheckOptions {
43
+ root: string;
44
+ definition?: WorkflowCapabilityDefinition;
45
+ extensionVersion?: string;
46
+ skillVersion?: string;
47
+ publishableFiles: readonly string[];
48
+ publicationOverrides?: Readonly<Partial<Record<PublicationPath, string>>>;
49
+ contextMeasurement?: string;
50
+ guidanceBaseline?: string;
51
+ authoringCoverage?: readonly WorkflowAuthoringCoverageEntry[];
52
+ guidanceOverrides?: Readonly<Record<string, string>>;
53
+ }
54
+
55
+ const SKILL_ROOT = "skills/workflow-authoring";
56
+ /** Package-relative generated hash baseline for compact and detailed guidance. */
57
+ export const WORKFLOW_GUIDANCE_BASELINE_PATH = "docs/workflow-guidance-baseline.json";
58
+ const FOCUSED_REFERENCES = [
59
+ "capabilities",
60
+ "capability-details",
61
+ "runtime",
62
+ "helpers",
63
+ "common-helpers",
64
+ "quality-helpers",
65
+ "retry-helper",
66
+ "specialized-helpers",
67
+ "lifecycle",
68
+ "versions",
69
+ "pattern-selection",
70
+ "focused-recipes",
71
+ "registry-ownership",
72
+ "review",
73
+ "debugging",
74
+ ] as const;
75
+ const PATTERNS = [
76
+ "classify-and-act",
77
+ "fan-out-and-synthesize",
78
+ "adversarial-verification",
79
+ "generate-and-filter",
80
+ "tournament",
81
+ "loop-until-done",
82
+ ] as const;
83
+ const RECIPES = ["phased-budgets", "saved-nested-workflows", "bounded-semantic-retry", "structured-output"] as const;
84
+
85
+ /** Skill files that must be present in the publishable npm package. */
86
+ export const REQUIRED_WORKFLOW_PACKAGE_RESOURCES = [
87
+ `${SKILL_ROOT}/SKILL.md`,
88
+ ...FOCUSED_REFERENCES.map((name) => `${SKILL_ROOT}/references/${name}.md`),
89
+ ...PATTERNS.map((name) => `${SKILL_ROOT}/examples/${name}.js`),
90
+ ...RECIPES.map((name) => `${SKILL_ROOT}/examples/${name}.js`),
91
+ ] as const;
92
+
93
+ function diagnostic(
94
+ code: WorkflowReleaseDiagnosticCode,
95
+ subject: string,
96
+ message: string,
97
+ severity: WorkflowReleaseDiagnostic["severity"] = "error",
98
+ ): WorkflowReleaseDiagnostic {
99
+ return { code, severity, subject, message };
100
+ }
101
+
102
+ function skillVersion(root: string): string | null {
103
+ const skill = readFileSync(join(root, SKILL_ROOT, "SKILL.md"), "utf8");
104
+ return /^\s{2}version:\s*["']?([^"'\s]+)["']?\s*$/m.exec(skill)?.[1] ?? null;
105
+ }
106
+
107
+ function anchorExists(markdown: string, anchor: string): boolean {
108
+ if (markdown.includes(`<a id="${anchor}"></a>`)) return true;
109
+ return markdown
110
+ .split("\n")
111
+ .filter((line) => /^#{1,6} /.test(line))
112
+ .map((line) =>
113
+ line
114
+ .replace(/^#{1,6} /, "")
115
+ .toLowerCase()
116
+ .replace(/[^a-z0-9 -]/g, "")
117
+ .trim()
118
+ .replace(/ +/g, "-"),
119
+ )
120
+ .includes(anchor);
121
+ }
122
+
123
+ function validateVersions(
124
+ definition: WorkflowCapabilityDefinition,
125
+ extensionVersion: string,
126
+ installedSkillVersion: string | null,
127
+ ): WorkflowReleaseDiagnostic[] {
128
+ const diagnostics: WorkflowReleaseDiagnostic[] = [];
129
+ const versions: Array<[string, string | null]> = [
130
+ ["contract extension", definition.versions.extension],
131
+ ["contract content", definition.versions.content.version],
132
+ ["installed skill", installedSkillVersion],
133
+ ];
134
+ for (const [subject, actual] of versions) {
135
+ if (actual !== extensionVersion) {
136
+ diagnostics.push(
137
+ diagnostic(
138
+ WorkflowReleaseDiagnosticCode.INCOMPATIBLE_VERSION,
139
+ subject,
140
+ `${subject} version ${actual ?? "<missing>"} must match extension version ${extensionVersion}.`,
141
+ ),
142
+ );
143
+ }
144
+ }
145
+ if (definition.versions.format.kind !== "present-at" || !/^1(?:\.|$)/.test(definition.versions.format.version)) {
146
+ diagnostics.push(
147
+ diagnostic(
148
+ WorkflowReleaseDiagnosticCode.INCOMPATIBLE_VERSION,
149
+ "contract format",
150
+ `Contract format ${definition.versions.format.version} is incompatible with supported format major 1.`,
151
+ ),
152
+ );
153
+ }
154
+ return diagnostics;
155
+ }
156
+
157
+ function validateCapabilityLinks(root: string, definition: WorkflowCapabilityDefinition): WorkflowReleaseDiagnostic[] {
158
+ const diagnostics: WorkflowReleaseDiagnostic[] = [];
159
+ for (const capability of definition.capabilities) {
160
+ if (
161
+ capability.support === CapabilitySupport.SUPPORTED &&
162
+ capability.discovery !== DiscoveryPlacement.NONE &&
163
+ capability.behaviorEvidence.length === 0
164
+ ) {
165
+ diagnostics.push(
166
+ diagnostic(
167
+ WorkflowReleaseDiagnosticCode.MISSING_BEHAVIOR_EVIDENCE,
168
+ capability.id,
169
+ `Advertised capability ${capability.id} has no behavior-test evidence.`,
170
+ ),
171
+ );
172
+ }
173
+ for (const evidence of capability.behaviorEvidence) {
174
+ if (!existsSync(join(root, evidence))) {
175
+ diagnostics.push(
176
+ diagnostic(
177
+ WorkflowReleaseDiagnosticCode.UNRESOLVED_BEHAVIOR_EVIDENCE,
178
+ evidence,
179
+ `Capability ${capability.id} references missing behavior test ${evidence}.`,
180
+ ),
181
+ );
182
+ }
183
+ }
184
+ if (capability.staticReference) {
185
+ const { path, anchor } = capability.staticReference;
186
+ const absolute = join(root, path);
187
+ const subject = `${path}#${anchor}`;
188
+ if (!existsSync(absolute) || !anchorExists(readFileSync(absolute, "utf8"), anchor)) {
189
+ diagnostics.push(
190
+ diagnostic(
191
+ WorkflowReleaseDiagnosticCode.BROKEN_CONTRACT_REFERENCE,
192
+ subject,
193
+ `Capability ${capability.id} references unresolved installed documentation ${subject}.`,
194
+ ),
195
+ );
196
+ }
197
+ }
198
+ }
199
+ return diagnostics;
200
+ }
201
+
202
+ function validateAuthoringCoverage(
203
+ root: string,
204
+ definition: WorkflowCapabilityDefinition,
205
+ coverage: readonly WorkflowAuthoringCoverageEntry[],
206
+ guidanceOverrides: Readonly<Record<string, string>> = {},
207
+ ): WorkflowReleaseDiagnostic[] {
208
+ const expectedIds = [
209
+ ...definition.capabilities
210
+ .filter(
211
+ ({ discovery, support }) => discovery !== DiscoveryPlacement.NONE && support !== CapabilitySupport.INTERNAL,
212
+ )
213
+ .map(({ id }) => id),
214
+ ...WORKFLOW_AUTHORING_PATTERN_IDS,
215
+ ...WORKFLOW_AUTHORING_RECIPE_IDS,
216
+ ];
217
+ const observed = new Set(coverage.map(({ id }) => id));
218
+ const diagnostics = expectedIds
219
+ .filter((id) => !observed.has(id))
220
+ .map((id) =>
221
+ diagnostic(
222
+ WorkflowReleaseDiagnosticCode.MISSING_AUTHORING_COVERAGE,
223
+ id,
224
+ `Stable workflow authoring surface ${id} is absent from the capability coverage manifest.`,
225
+ ),
226
+ );
227
+
228
+ const knownScenarioIds = new Set(WORKFLOW_COMPREHENSION_SCENARIO_IDS);
229
+ for (const entry of coverage.filter(
230
+ ({ protection }) => protection === WorkflowAuthoringProtection.BEHAVIORALLY_COVERED,
231
+ )) {
232
+ if (
233
+ entry.comprehensionScenarios.length === 0 ||
234
+ entry.comprehensionScenarios.some((scenarioId) => !knownScenarioIds.has(scenarioId))
235
+ ) {
236
+ diagnostics.push(
237
+ diagnostic(
238
+ WorkflowReleaseDiagnosticCode.UNKNOWN_COMPREHENSION_SCENARIO,
239
+ entry.id,
240
+ `Behaviorally covered authoring surface ${entry.id} references a missing comprehension scenario.`,
241
+ ),
242
+ );
243
+ }
244
+ }
245
+
246
+ for (const entry of coverage.filter(({ protection }) => protection === WorkflowAuthoringProtection.GUIDANCE_FROZEN)) {
247
+ if (entry.protectedGuidance.length === 0) {
248
+ diagnostics.push(
249
+ diagnostic(
250
+ WorkflowReleaseDiagnosticCode.UNPROTECTED_AUTHORING_GUIDANCE,
251
+ entry.id,
252
+ `Untested authoring surface ${entry.id} has no frozen guidance or routing pointer.`,
253
+ ),
254
+ );
255
+ continue;
256
+ }
257
+ const drifted = entry.protectedGuidance.find(({ path, anchor, requiredText }) => {
258
+ const absolute = join(root, path);
259
+ if (!existsSync(absolute) && guidanceOverrides[path] === undefined) return true;
260
+ const source = guidanceOverrides[path] ?? readFileSync(absolute, "utf8");
261
+ return (
262
+ (anchor !== undefined && !anchorExists(source, anchor)) ||
263
+ (requiredText !== undefined && !source.includes(requiredText))
264
+ );
265
+ });
266
+ if (drifted) {
267
+ const absolute = join(root, drifted.path);
268
+ const source =
269
+ !existsSync(absolute) && guidanceOverrides[drifted.path] === undefined
270
+ ? null
271
+ : (guidanceOverrides[drifted.path] ?? readFileSync(absolute, "utf8"));
272
+ const failedChecks: string[] = [];
273
+ if (source === null) {
274
+ failedChecks.push("protected file");
275
+ }
276
+ if (source !== null && drifted.anchor !== undefined && !anchorExists(source, drifted.anchor)) {
277
+ failedChecks.push("required anchor");
278
+ }
279
+ if (source !== null && drifted.requiredText !== undefined && !source.includes(drifted.requiredText)) {
280
+ failedChecks.push("required text");
281
+ }
282
+ diagnostics.push(
283
+ diagnostic(
284
+ WorkflowReleaseDiagnosticCode.PROTECTED_GUIDANCE_DRIFT,
285
+ entry.id,
286
+ `Protected guidance for ${entry.id} no longer matches the ${failedChecks.join(" and ")} in ${drifted.path}. Restore it to undo an accidental change. For an intentional change, inspect the coverage manifest and relevant behavioral/provider evidence before deliberately updating the corresponding anchor/text in src/workflow-authoring-coverage.ts. See CONTRIBUTING.md#protected-workflow-authoring-guidance.`,
287
+ ),
288
+ );
289
+ }
290
+ }
291
+ return diagnostics;
292
+ }
293
+
294
+ function validateFrozenGuidanceFiles(
295
+ root: string,
296
+ guidanceOverrides: Readonly<Record<string, string>> = {},
297
+ ): WorkflowReleaseDiagnostic[] {
298
+ return WORKFLOW_AUTHORING_FROZEN_FILES.flatMap(({ path, sha256: expected }) => {
299
+ const absolute = join(root, path);
300
+ if (!existsSync(absolute) && guidanceOverrides[path] === undefined) {
301
+ return [
302
+ diagnostic(
303
+ WorkflowReleaseDiagnosticCode.PROTECTED_GUIDANCE_DRIFT,
304
+ path,
305
+ `Protected workflow-authoring file is missing: ${path}. Restore an accidental deletion. An intentional removal requires review of the coverage manifest and relevant behavioral/provider evidence, followed by deliberate updates to authoring coverage and package resources. See CONTRIBUTING.md#protected-workflow-authoring-guidance.`,
306
+ ),
307
+ ];
308
+ }
309
+ const source = guidanceOverrides[path] ?? readFileSync(absolute, "utf8");
310
+ return sha256(source) === expected
311
+ ? []
312
+ : [
313
+ diagnostic(
314
+ WorkflowReleaseDiagnosticCode.PROTECTED_GUIDANCE_DRIFT,
315
+ path,
316
+ `Protected workflow-authoring file changed: ${path}. Its SHA-256 is an explicit review checkpoint for guidance with partial behavioral coverage. Revert accidental changes. For an intentional change, inspect the coverage manifest and relevant behavioral tests, plus provider evidence when needed, then run npm run guidance:accept -- ${path}. This updates the matching sha256 in WORKFLOW_AUTHORING_FROZEN_FILES (src/workflow-authoring-coverage.ts). See CONTRIBUTING.md#protected-workflow-authoring-guidance.`,
317
+ ),
318
+ ];
319
+ });
320
+ }
321
+
322
+ function validateToolInputs(definition: WorkflowCapabilityDefinition): WorkflowReleaseDiagnostic[] {
323
+ const { parameters } = createWorkflowTool();
324
+ const properties: Record<string, unknown> =
325
+ isRecord(parameters) && isRecord(parameters.properties) ? parameters.properties : {};
326
+ const observed = new Set(Object.keys(properties));
327
+ const declared = new Set(
328
+ definition.capabilities
329
+ .filter((capability) => capability.classification === CapabilityClassification.WORKFLOW_TOOL_INPUT)
330
+ .map((capability) => capability.label),
331
+ );
332
+ const diagnostics: WorkflowReleaseDiagnostic[] = [];
333
+ for (const name of declared) {
334
+ if (!observed.has(name)) {
335
+ diagnostics.push(
336
+ diagnostic(
337
+ WorkflowReleaseDiagnosticCode.TOOL_INPUT_MISMATCH,
338
+ name,
339
+ `Declared workflow-tool input ${name} is absent from the runtime schema.`,
340
+ ),
341
+ );
342
+ }
343
+ }
344
+ for (const name of observed) {
345
+ if (!declared.has(name)) {
346
+ diagnostics.push(
347
+ diagnostic(
348
+ WorkflowReleaseDiagnosticCode.TOOL_INPUT_MISMATCH,
349
+ name,
350
+ `Runtime schema exposes undeclared workflow-tool input ${name}.`,
351
+ ),
352
+ );
353
+ }
354
+ }
355
+
356
+ const tokenBudget = definition.capabilities.find(
357
+ (capability) =>
358
+ capability.classification === CapabilityClassification.WORKFLOW_TOOL_INPUT && capability.label === "tokenBudget",
359
+ );
360
+ const tokenBudgetSchema = properties.tokenBudget;
361
+ const observedTokenBudget =
362
+ isRecord(tokenBudgetSchema) && typeof tokenBudgetSchema.description === "string"
363
+ ? tokenBudgetSchema.description
364
+ : "";
365
+ const contractCallsSoft = tokenBudget?.constraints.some((constraint) => /soft/i.test(constraint)) ?? false;
366
+ const contractCallsHard = tokenBudget?.constraints.some((constraint) => /hard/i.test(constraint)) ?? false;
367
+ const proseCallsSoft = /soft/i.test(observedTokenBudget);
368
+ const proseCallsHard = /hard/i.test(observedTokenBudget);
369
+ if ((contractCallsSoft && proseCallsHard) || (contractCallsHard && proseCallsSoft)) {
370
+ diagnostics.push(
371
+ diagnostic(
372
+ WorkflowReleaseDiagnosticCode.RUNTIME_CONSTRAINT_DISAGREEMENT,
373
+ "tokenBudget",
374
+ `Contract constraints and provider-visible tool prose disagree about whether tokenBudget is a soft or hard gate. Contract: ${tokenBudget?.constraints.join("; ")}. Tool prose: ${observedTokenBudget}`,
375
+ ),
376
+ );
377
+ }
378
+ return diagnostics;
379
+ }
380
+
381
+ function isRecord(value: unknown): value is Record<string, unknown> {
382
+ return typeof value === "object" && value !== null && !Array.isArray(value);
383
+ }
384
+
385
+ function sha256(value: string): string {
386
+ return createHash("sha256").update(value).digest("hex");
387
+ }
388
+
389
+ /** Render deterministic hashes for provider-visible and on-demand guidance surfaces. */
390
+ export function renderWorkflowGuidanceBaseline(root: string): string {
391
+ const tool = createWorkflowTool();
392
+ const compact = JSON.stringify({
393
+ description: tool.description,
394
+ parameters: tool.parameters,
395
+ promptSnippet: tool.promptSnippet,
396
+ promptGuidelines: tool.promptGuidelines,
397
+ });
398
+ const detailedPaths = [
399
+ `${SKILL_ROOT}/SKILL.md`,
400
+ ...FOCUSED_REFERENCES.filter((name) => name !== "capabilities" && name !== "capability-details").map(
401
+ (name) => `${SKILL_ROOT}/references/${name}.md`,
402
+ ),
403
+ ];
404
+ const detailed = detailedPaths.map((path) => `${path}\n${readFileSync(join(root, path), "utf8")}`).join("\n");
405
+ return `${JSON.stringify(
406
+ {
407
+ formatVersion: 1,
408
+ algorithm: "sha256",
409
+ surfaces: {
410
+ compactGuidance: sha256(compact),
411
+ detailedProse: sha256(detailed),
412
+ },
413
+ },
414
+ null,
415
+ 2,
416
+ )}\n`;
417
+ }
418
+
419
+ /** Refresh the committed guidance hash baseline under root. */
420
+ export function writeWorkflowGuidanceBaseline(root: string): void {
421
+ writeFileSync(join(root, WORKFLOW_GUIDANCE_BASELINE_PATH), renderWorkflowGuidanceBaseline(root));
422
+ }
423
+
424
+ function validateGuidanceBaseline(root: string, actual?: string): WorkflowReleaseDiagnostic[] {
425
+ const committed = actual ?? readFileSync(join(root, WORKFLOW_GUIDANCE_BASELINE_PATH), "utf8");
426
+ if (committed === renderWorkflowGuidanceBaseline(root)) return [];
427
+ return [
428
+ diagnostic(
429
+ WorkflowReleaseDiagnosticCode.NON_CONTRACTUAL_PROSE_DRIFT,
430
+ WORKFLOW_GUIDANCE_BASELINE_PATH,
431
+ `Compact guidance or detailed hand-written prose changed; review intent and refresh ${WORKFLOW_GUIDANCE_BASELINE_PATH} if intentional.`,
432
+ "warning",
433
+ ),
434
+ ];
435
+ }
436
+
437
+ function validatePackage(root: string, publishableFiles: readonly string[]): WorkflowReleaseDiagnostic[] {
438
+ const diagnostics: WorkflowReleaseDiagnostic[] = [];
439
+ const files = new Set(publishableFiles);
440
+ for (const resource of REQUIRED_WORKFLOW_PACKAGE_RESOURCES) {
441
+ if (!files.has(resource)) {
442
+ diagnostics.push(
443
+ diagnostic(
444
+ WorkflowReleaseDiagnosticCode.MISSING_PACKAGE_RESOURCE,
445
+ resource,
446
+ `Publishable package omitted required workflow resource ${resource}.`,
447
+ ),
448
+ );
449
+ }
450
+ }
451
+
452
+ for (const sourcePath of publishableFiles.filter(
453
+ (path) => path.startsWith(`${SKILL_ROOT}/`) && path.endsWith(".md"),
454
+ )) {
455
+ const source = readFileSync(join(root, sourcePath), "utf8");
456
+ for (const match of source.matchAll(/\[[^\]]+\]\(([^)#]+)(?:#([^)]+))?\)/g)) {
457
+ const target = normalize(join(dirname(sourcePath), match[1]));
458
+ const anchor = match[2];
459
+ const outsidePackage = relative(".", target).startsWith("..");
460
+ const targetMissing = !files.has(target);
461
+ const targetSource = !targetMissing && anchor ? readFileSync(join(root, target), "utf8") : null;
462
+ if (outsidePackage || targetMissing || (anchor && targetSource !== null && !anchorExists(targetSource, anchor))) {
463
+ const subject = `${sourcePath} -> ${target}${anchor ? `#${anchor}` : ""}`;
464
+ diagnostics.push(
465
+ diagnostic(
466
+ WorkflowReleaseDiagnosticCode.BROKEN_PACKAGE_LINK,
467
+ subject,
468
+ `Packaged workflow skill link does not resolve: ${subject}.`,
469
+ ),
470
+ );
471
+ }
472
+ }
473
+ }
474
+ return diagnostics;
475
+ }
476
+
477
+ /** Parse publishable paths from `npm pack --dry-run --json` without trusting external JSON shapes. */
478
+ export function parseNpmPackFilePaths(output: string): string[] {
479
+ const parsed: unknown = JSON.parse(output);
480
+ if (!Array.isArray(parsed)) {
481
+ return [];
482
+ }
483
+ const first = parsed[0];
484
+ if (!isRecord(first) || !Array.isArray(first.files)) {
485
+ return [];
486
+ }
487
+ return first.files.flatMap((file: unknown) => (isRecord(file) && typeof file.path === "string" ? [file.path] : []));
488
+ }
489
+
490
+ /** Return every model-free contract, package, documentation, and guidance alignment diagnostic. */
491
+ export function checkWorkflowRelease(options: WorkflowReleaseCheckOptions): WorkflowReleaseDiagnostic[] {
492
+ const definition = options.definition ?? WORKFLOW_CAPABILITY_DEFINITION;
493
+ const extensionVersion = options.extensionVersion ?? packageJson.version;
494
+ const installedSkillVersion = options.skillVersion ?? skillVersion(options.root);
495
+ const diagnostics = [
496
+ ...validateVersions(definition, extensionVersion, installedSkillVersion),
497
+ ...validateCapabilityLinks(options.root, definition),
498
+ ...validateAuthoringCoverage(
499
+ options.root,
500
+ definition,
501
+ options.authoringCoverage ?? WORKFLOW_AUTHORING_COVERAGE,
502
+ options.guidanceOverrides,
503
+ ),
504
+ ...validateFrozenGuidanceFiles(options.root, options.guidanceOverrides),
505
+ ...validateToolInputs(definition),
506
+ ...validatePackage(options.root, options.publishableFiles),
507
+ ...validateGuidanceBaseline(options.root, options.guidanceBaseline),
508
+ ];
509
+
510
+ for (const path of checkWorkflowCapabilityPublications(options.root, options.publicationOverrides)) {
511
+ diagnostics.push(
512
+ diagnostic(
513
+ WorkflowReleaseDiagnosticCode.STALE_GENERATED_SURFACE,
514
+ path,
515
+ `Generated workflow capability publication is stale: ${path}.`,
516
+ ),
517
+ );
518
+ }
519
+ if (!checkWorkflowContextMeasurement(options.root, options.contextMeasurement)) {
520
+ diagnostics.push(
521
+ diagnostic(
522
+ WorkflowReleaseDiagnosticCode.STALE_GENERATED_SURFACE,
523
+ WORKFLOW_CONTEXT_MEASUREMENT_PATH,
524
+ `Generated workflow context measurement is stale: ${WORKFLOW_CONTEXT_MEASUREMENT_PATH}.`,
525
+ ),
526
+ );
527
+ }
528
+ return diagnostics;
529
+ }