@williambeto/ai-workflow 2.9.4 → 2.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  ## [unreleased]
2
2
 
3
+ ## [2.9.6] - 2026-07-19
4
+
5
+ ### 🐛 Bug Fixes
6
+
7
+ - Include committed release changes in evidence through an explicit Git base
8
+ reference while preserving current working-tree behavior by default
9
+ - Replace duplicate aggregate validation inside the E2E suite with an exact
10
+ registry contract, keeping the observed aggregate gate in its dedicated CI
11
+ step
12
+
13
+ ### 🛡️ Hardening
14
+
15
+ - Enforce TypeScript source checking in local validation and GitHub Actions
16
+ - Point internal architecture documentation to existing canonical release,
17
+ template inventory, and status sources, with release-readiness regression
18
+ coverage
19
+
20
+ ### 📦 Dependencies
21
+
22
+ - Update `@opencode-ai/sdk` from 1.17.18 to 1.18.3 after package-diff,
23
+ adapter, consumer, and packaging compatibility validation
24
+
25
+ ### 🧪 Testing
26
+
27
+ - Cover committed, uncommitted, added, deleted, and invalid-ref release
28
+ evidence scopes
29
+ - Preserve the complete 23-check validation registry while removing a
30
+ redundant network and browser execution path from E2E
31
+
32
+ ## [2.9.5] - 2026-07-17
33
+
34
+ ### 🛡️ Hardening
35
+
36
+ - Declare the 17 required command-to-policy relationships through a
37
+ runtime-neutral canonical policy section
38
+ - Fail validation when required command policy references are missing,
39
+ duplicated, unknown, or unexpected
40
+
41
+ ### 🧪 Testing
42
+
43
+ - Add data-driven contract coverage for the complete policy reference map
44
+ - Verify referenced policies remain available in OpenCode installations and
45
+ Codex command skills
46
+
3
47
  ## [2.9.4] - 2026-07-16
4
48
 
5
49
  ### 🛡️ Hardening
@@ -23,18 +23,18 @@ import {
23
23
  isTerminalFailure,
24
24
  readPackageFile,
25
25
  resolveWorkflowProfile
26
- } from "../chunk-JDSEOEYW.js";
26
+ } from "../chunk-E7DHZFTN.js";
27
27
  import {
28
28
  EvidenceValidator,
29
29
  require_ajv,
30
30
  require_codegen
31
- } from "../chunk-FNT7DN3N.js";
31
+ } from "../chunk-XRYGVFHF.js";
32
32
  import {
33
33
  EvidenceCollector,
34
34
  QualityGuard,
35
35
  ValidationPlanner,
36
36
  parseDeliveryOutcomeClaim
37
- } from "../chunk-PP42PB7M.js";
37
+ } from "../chunk-D2W2ZW3Y.js";
38
38
  import {
39
39
  DeliveryDecisionEngine
40
40
  } from "../chunk-H7GIKXFO.js";
@@ -2194,6 +2194,9 @@ async function runDoctor({ cwd }) {
2194
2194
  }
2195
2195
  }
2196
2196
 
2197
+ // src/cli/commands/collect-evidence.ts
2198
+ import { spawnSync } from "child_process";
2199
+
2197
2200
  // src/core/validation/canonical-finalization.ts
2198
2201
  var DELIVERY_SUMMARY_FIELDS = Object.freeze([
2199
2202
  "Status",
@@ -2217,6 +2220,93 @@ function formatDeliverySummary(summary) {
2217
2220
  }
2218
2221
 
2219
2222
  // src/cli/commands/collect-evidence.ts
2223
+ function runGit(cwd, args, errorMessage) {
2224
+ const result = spawnSync("git", args, { cwd, encoding: "utf8" });
2225
+ if (result.status !== 0) {
2226
+ const detail = String(result.stderr || result.error?.message || "unknown Git error").trim();
2227
+ throw new Error(`${errorMessage}${detail ? `: ${detail}` : ""}`);
2228
+ }
2229
+ return String(result.stdout || "");
2230
+ }
2231
+ function parseNameStatus(output) {
2232
+ const changedFiles = /* @__PURE__ */ new Set();
2233
+ const addedFiles = /* @__PURE__ */ new Set();
2234
+ const deletedFiles = /* @__PURE__ */ new Set();
2235
+ const tokens = output.split("\0").filter(Boolean);
2236
+ for (let index = 0; index < tokens.length; ) {
2237
+ const entry = tokens[index++];
2238
+ const tabIndex = entry.indexOf(" ");
2239
+ const status = tabIndex >= 0 ? entry.slice(0, tabIndex) : entry;
2240
+ const firstPath = tabIndex >= 0 ? entry.slice(tabIndex + 1) : tokens[index++];
2241
+ if (!firstPath) continue;
2242
+ if (status.startsWith("R")) {
2243
+ const nextPath = tokens[index++];
2244
+ changedFiles.add(firstPath);
2245
+ deletedFiles.add(firstPath);
2246
+ if (nextPath) {
2247
+ changedFiles.add(nextPath);
2248
+ addedFiles.add(nextPath);
2249
+ }
2250
+ continue;
2251
+ }
2252
+ if (status.startsWith("C")) {
2253
+ const nextPath = tokens[index++];
2254
+ if (nextPath) {
2255
+ changedFiles.add(nextPath);
2256
+ addedFiles.add(nextPath);
2257
+ }
2258
+ continue;
2259
+ }
2260
+ changedFiles.add(firstPath);
2261
+ if (status.startsWith("A")) addedFiles.add(firstPath);
2262
+ if (status.startsWith("D")) deletedFiles.add(firstPath);
2263
+ }
2264
+ return {
2265
+ changedFiles: [...changedFiles],
2266
+ addedFiles: [...addedFiles],
2267
+ deletedFiles: [...deletedFiles]
2268
+ };
2269
+ }
2270
+ function resolveGitDiffScope(cwd, baseRef) {
2271
+ const normalizedRef = baseRef.trim();
2272
+ if (!normalizedRef) throw new Error("--base-ref must not be empty");
2273
+ const baseSha = runGit(
2274
+ cwd,
2275
+ ["rev-parse", "--verify", "--end-of-options", `${normalizedRef}^{commit}`],
2276
+ `Invalid --base-ref '${normalizedRef}'`
2277
+ ).trim();
2278
+ const mergeBase = runGit(
2279
+ cwd,
2280
+ ["merge-base", baseSha, "HEAD"],
2281
+ `Cannot determine a merge base for --base-ref '${normalizedRef}'`
2282
+ ).trim();
2283
+ const committed = parseNameStatus(runGit(
2284
+ cwd,
2285
+ ["diff", "--name-status", "-z", "--find-renames", `${mergeBase}..HEAD`],
2286
+ `Cannot inspect committed changes from --base-ref '${normalizedRef}'`
2287
+ ));
2288
+ const workingTree = parseNameStatus(runGit(
2289
+ cwd,
2290
+ ["diff", "--name-status", "-z", "--find-renames", "HEAD"],
2291
+ "Cannot inspect uncommitted changes"
2292
+ ));
2293
+ const untracked = runGit(
2294
+ cwd,
2295
+ ["ls-files", "--others", "--exclude-standard", "-z"],
2296
+ "Cannot inspect untracked files"
2297
+ ).split("\0").filter(Boolean);
2298
+ const changedFiles = /* @__PURE__ */ new Set([...committed.changedFiles, ...workingTree.changedFiles, ...untracked]);
2299
+ const addedFiles = /* @__PURE__ */ new Set([...committed.addedFiles, ...workingTree.addedFiles, ...untracked]);
2300
+ const deletedFiles = /* @__PURE__ */ new Set([...committed.deletedFiles, ...workingTree.deletedFiles]);
2301
+ for (const file of addedFiles) {
2302
+ if (deletedFiles.delete(file)) addedFiles.delete(file);
2303
+ }
2304
+ return {
2305
+ changedFiles: [...changedFiles].sort(),
2306
+ addedFiles: [...addedFiles].sort(),
2307
+ deletedFiles: [...deletedFiles].sort()
2308
+ };
2309
+ }
2220
2310
  function resolvePoliciesFromLegacyMode(mode, evidencePolicy) {
2221
2311
  const activePolicy = evidencePolicy || (mode === "full" ? "required" : "optional");
2222
2312
  let risk = "medium";
@@ -2246,7 +2336,8 @@ async function runCollectEvidence({
2246
2336
  claimedOutcome = null,
2247
2337
  deliveryChangedFiles = null,
2248
2338
  deliveryAddedFiles = null,
2249
- deliveryDeletedFiles = null
2339
+ deliveryDeletedFiles = null,
2340
+ baseRef = null
2250
2341
  }) {
2251
2342
  if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
2252
2343
  console.log("[FINALIZATION DEFERRED] Parent ai-workflow execute process owns canonical finalization.");
@@ -2254,7 +2345,8 @@ async function runCollectEvidence({
2254
2345
  }
2255
2346
  const policies = resolvePoliciesFromLegacyMode(mode, evidencePolicy);
2256
2347
  const workspaceGuard = new QualityGuard({ cwd, taskSlug, evidencePolicy: policies.evidencePolicy, riskLevel: policies.riskLevel });
2257
- const observedDeliveryFiles = deliveryChangedFiles ?? workspaceGuard.getChangedFiles();
2348
+ const gitDiffScope = deliveryChangedFiles === null && baseRef ? resolveGitDiffScope(cwd, baseRef) : null;
2349
+ const observedDeliveryFiles = deliveryChangedFiles ?? gitDiffScope?.changedFiles ?? workspaceGuard.getChangedFiles();
2258
2350
  const qualityGuard = new QualityGuard({
2259
2351
  cwd,
2260
2352
  taskSlug,
@@ -2291,8 +2383,8 @@ async function runCollectEvidence({
2291
2383
  workflowEvidence,
2292
2384
  claimedOutcome,
2293
2385
  deliveryChangedFiles: observedDeliveryFiles,
2294
- deliveryAddedFiles,
2295
- deliveryDeletedFiles,
2386
+ deliveryAddedFiles: deliveryAddedFiles ?? gitDiffScope?.addedFiles,
2387
+ deliveryDeletedFiles: deliveryDeletedFiles ?? gitDiffScope?.deletedFiles,
2296
2388
  riskLevel: policies.riskLevel
2297
2389
  });
2298
2390
  const evidence = await collector.collect(tasks, { writeArtifact: persist });
@@ -2307,7 +2399,7 @@ async function runCollectEvidence({
2307
2399
  }
2308
2400
 
2309
2401
  // src/core/healing/cli-remediation-executor.ts
2310
- import { spawnSync, execSync as execSync2 } from "child_process";
2402
+ import { spawnSync as spawnSync2, execSync as execSync2 } from "child_process";
2311
2403
  import fs8 from "fs/promises";
2312
2404
  import path10 from "path";
2313
2405
  async function readScripts(cwd) {
@@ -2371,7 +2463,7 @@ function createCliRemediationExecutor(cwd) {
2371
2463
  process.stdout.write(` \u2192 npm run ${scriptName} ... `);
2372
2464
  const isWin32 = process.platform === "win32";
2373
2465
  const cmd = isWin32 ? "npm.cmd" : "npm";
2374
- const result = spawnSync(cmd, ["run", scriptName], {
2466
+ const result = spawnSync2(cmd, ["run", scriptName], {
2375
2467
  cwd,
2376
2468
  shell: false,
2377
2469
  encoding: "utf8",
@@ -2440,6 +2532,8 @@ function stripMarkdownFence(content) {
2440
2532
  }
2441
2533
 
2442
2534
  // src/core/sdd/approval-receipt.ts
2535
+ var Ajv = import_ajv.default;
2536
+ var addFormats = import_ajv_formats.default;
2443
2537
  var ApprovalReceiptValidator = class {
2444
2538
  cwd;
2445
2539
  constructor({ cwd = process.cwd() } = {}) {
@@ -2463,8 +2557,8 @@ var ApprovalReceiptValidator = class {
2463
2557
  receipt = JSON.parse(await fs10.readFile(absoluteReceiptPath, "utf8"));
2464
2558
  const root = getPackageRoot();
2465
2559
  const schema = JSON.parse(await fs10.readFile(path12.join(root, "dist-assets/schemas/approval-receipt.schema.json"), "utf8"));
2466
- const ajv = new import_ajv.default({ allErrors: true });
2467
- (0, import_ajv_formats.default)(ajv);
2560
+ const ajv = new Ajv({ allErrors: true });
2561
+ addFormats(ajv);
2468
2562
  const validateSchema = ajv.compile(schema);
2469
2563
  if (!validateSchema(receipt)) {
2470
2564
  const errors = (validateSchema.errors || []).map((error) => `${error.instancePath} ${error.message}`).join(", ");
@@ -2488,6 +2582,7 @@ var ApprovalReceiptValidator = class {
2488
2582
  var import_ajv2 = __toESM(require_ajv(), 1);
2489
2583
  import fs11 from "fs/promises";
2490
2584
  import path13 from "path";
2585
+ var Ajv2 = import_ajv2.default;
2491
2586
  function isContained(root, candidate) {
2492
2587
  return candidate === root || candidate.startsWith(`${root}${path13.sep}`);
2493
2588
  }
@@ -2542,7 +2637,7 @@ var TechnicalPlanValidator = class {
2542
2637
  }
2543
2638
  try {
2544
2639
  const schema = JSON.parse(await fs11.readFile(path13.join(getPackageRoot(), "dist-assets/schemas/technical-plan.schema.json"), "utf8"));
2545
- const validateSchema = new import_ajv2.default({ allErrors: true }).compile(schema);
2640
+ const validateSchema = new Ajv2({ allErrors: true }).compile(schema);
2546
2641
  if (!validateSchema(plan)) {
2547
2642
  const errors = (validateSchema.errors || []).map((error) => `${error.instancePath} ${error.message}`).join(", ");
2548
2643
  return { valid: false, reason: `Technical plan schema violation: ${errors}` };
@@ -3351,7 +3446,7 @@ async function runFastTrackValidation(cwd, result, checkBranchSafety) {
3351
3446
  const evidencePath = path16.join(cwd, "EVIDENCE.json");
3352
3447
  const hasEvidence = await fs14.access(evidencePath).then(() => true).catch(() => false);
3353
3448
  if (hasEvidence) {
3354
- const { EvidenceValidator: EvidenceValidator2 } = await import("../evidence-validator-HS3NTWAB.js");
3449
+ const { EvidenceValidator: EvidenceValidator2 } = await import("../evidence-validator-6GD442JE.js");
3355
3450
  const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
3356
3451
  const evidenceValidator = new EvidenceValidator2({ cwd, skipFileCheck: isTest, skipGitCheck: isTest });
3357
3452
  const valResult = await evidenceValidator.validate();
@@ -3662,7 +3757,7 @@ Usage:
3662
3757
  ai-workflow run --spec-path=<path> [--approval-path=<path>] [--runtime=<opencode|codex>]
3663
3758
  ai-workflow init [--yes] [--force] [--dry-run] [--no-install] [--no-overwrite] [--claude] [--codex] [--antigravity] [--profile=<profile>]
3664
3759
  ai-workflow validate [--a11y] [--visual-dist=<path>] [--port=<port>]
3665
- ai-workflow collect-evidence [--task=<slug>] [--evidence-policy=<optional|required>] [--mode=<quick|standard|full>] [--dry-run] [--visual-dist=<path>] [--port=<port>]
3760
+ ai-workflow collect-evidence [--task=<slug>] [--evidence-policy=<optional|required>] [--mode=<quick|standard|full>] [--base-ref=<git-ref>] [--dry-run] [--visual-dist=<path>] [--port=<port>]
3666
3761
  ai-workflow doctor
3667
3762
  ai-workflow clean [--yes] [--dry-run] [--purge-agents]
3668
3763
  ai-workflow lint skills
@@ -3686,6 +3781,7 @@ function parseFlags(args) {
3686
3781
  const taskArg = args.find((arg) => arg.startsWith("--task="));
3687
3782
  const modeArg = args.find((arg) => arg.startsWith("--mode="));
3688
3783
  const evidencePolicyArg = args.find((arg) => arg.startsWith("--evidence-policy="));
3784
+ const baseRefArg = args.find((arg) => arg.startsWith("--base-ref="));
3689
3785
  const requestArg = args.find((arg) => arg.startsWith("--request="));
3690
3786
  const visualDistArg = args.find((arg) => arg.startsWith("--visual-dist="));
3691
3787
  const portArg = args.find((arg) => arg.startsWith("--port="));
@@ -3708,6 +3804,7 @@ function parseFlags(args) {
3708
3804
  taskSlug: taskArg ? taskArg.replace("--task=", "") : void 0,
3709
3805
  mode: modeArg ? modeArg.replace("--mode=", "") : void 0,
3710
3806
  evidencePolicy: evidencePolicyArg ? evidencePolicyArg.replace("--evidence-policy=", "") : void 0,
3807
+ baseRef: baseRefArg ? baseRefArg.replace("--base-ref=", "") : void 0,
3711
3808
  request: requestArg ? requestArg.replace("--request=", "") : void 0,
3712
3809
  visualDist: visualDistArg ? visualDistArg.replace("--visual-dist=", "") : void 0,
3713
3810
  port: portArg ? portArg.replace("--port=", "") : void 0,
@@ -3751,13 +3848,14 @@ var commandMap = {
3751
3848
  taskSlug: flags.taskSlug,
3752
3849
  mode: flags.mode,
3753
3850
  evidencePolicy: flags.evidencePolicy,
3851
+ baseRef: flags.baseRef,
3754
3852
  dryRun: flags.dryRun,
3755
3853
  visualDist: flags.visualDist,
3756
3854
  port: flags.port
3757
3855
  });
3758
3856
  },
3759
3857
  validate: async (_, flags) => {
3760
- const { runValidate } = await import("../validate-7G3HBE2K.js");
3858
+ const { runValidate } = await import("../validate-YMBSICJL.js");
3761
3859
  await runValidate({
3762
3860
  cwd: process.cwd(),
3763
3861
  taskSlug: flags.taskSlug,