@captain_z/zsk 1.8.2 → 1.8.4

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 (57) hide show
  1. package/dist/bin.js +4 -0
  2. package/dist/bin.js.map +1 -1
  3. package/dist/commands/add-flow.d.ts +3 -7
  4. package/dist/commands/add-flow.js +7 -59
  5. package/dist/commands/add-flow.js.map +1 -1
  6. package/dist/commands/add.js +25 -104
  7. package/dist/commands/add.js.map +1 -1
  8. package/dist/commands/check.js +184 -8
  9. package/dist/commands/check.js.map +1 -1
  10. package/dist/commands/gate.d.ts +2 -0
  11. package/dist/commands/gate.js +2 -0
  12. package/dist/commands/gate.js.map +1 -1
  13. package/dist/core/prepare-sync.d.ts +2 -31
  14. package/dist/core/prepare-sync.js +119 -136
  15. package/dist/core/prepare-sync.js.map +1 -1
  16. package/dist/core/profile-bundle-installation.d.ts +55 -0
  17. package/dist/core/profile-bundle-installation.js +170 -0
  18. package/dist/core/profile-bundle-installation.js.map +1 -0
  19. package/dist/core/source-snapshot-adapters.d.ts +59 -0
  20. package/dist/core/source-snapshot-adapters.js +82 -0
  21. package/dist/core/source-snapshot-adapters.js.map +1 -0
  22. package/dist/core/staffing-plan.d.ts +7 -1
  23. package/dist/core/staffing-plan.js +186 -29
  24. package/dist/core/staffing-plan.js.map +1 -1
  25. package/dist/core/stage-clarity-verification.d.ts +31 -0
  26. package/dist/core/stage-clarity-verification.js +313 -0
  27. package/dist/core/stage-clarity-verification.js.map +1 -0
  28. package/dist/core/stage-quality-artifacts.d.ts +15 -0
  29. package/dist/core/stage-quality-artifacts.js +421 -0
  30. package/dist/core/stage-quality-artifacts.js.map +1 -0
  31. package/dist/core/stage-quality-contracts.d.ts +86 -0
  32. package/dist/core/stage-quality-contracts.js +2 -0
  33. package/dist/core/stage-quality-contracts.js.map +1 -0
  34. package/dist/core/stage-quality-criteria.d.ts +9 -0
  35. package/dist/core/stage-quality-criteria.js +323 -0
  36. package/dist/core/stage-quality-criteria.js.map +1 -0
  37. package/dist/core/stage-quality-rendering.d.ts +13 -0
  38. package/dist/core/stage-quality-rendering.js +122 -0
  39. package/dist/core/stage-quality-rendering.js.map +1 -0
  40. package/dist/core/stage-quality.d.ts +5 -52
  41. package/dist/core/stage-quality.js +40 -432
  42. package/dist/core/stage-quality.js.map +1 -1
  43. package/dist/core/template-registry.js +6 -0
  44. package/dist/core/template-registry.js.map +1 -1
  45. package/package.json +2 -2
  46. package/templates/module/frontend-module/design.md +25 -3
  47. package/templates/module/frontend-module/proposal.md +8 -0
  48. package/templates/module/frontend-module/spec.md +16 -0
  49. package/templates/module/frontend-module/tasks.md +23 -7
  50. package/templates/project-init/.zsk/config.yaml +33 -0
  51. package/templates/project-init/.zsk/docs/PROJECT-CONFIG.md +43 -0
  52. package/templates/project-init/.zsk/docs/SYSTEM-SPEC.md +23 -1
  53. package/templates/project-init/.zsk/raws/index.md +19 -0
  54. package/templates/project-init/.zsk/raws/prepare/design/index.md +18 -0
  55. package/templates/project-init/.zsk/raws/prepare/index.md +33 -0
  56. package/templates/project-init/.zsk/raws/prepare/ux/index.md +19 -0
  57. package/templates/project-init/.zsk/roles.yaml +11 -11
@@ -5,6 +5,7 @@ import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path
5
5
  import { DESIGN_LOCAL_FILE_ORIGIN_KEYS, JIRA_LOCAL_FILE_ORIGIN_KEYS, flattenProjectSources, resolveSourceSnapshot, } from "./config.js";
6
6
  import { inferSourceOrigin } from "./origin-detection.js";
7
7
  import { describeResource, updateRawIndexes, updateRawManifest } from "./raw-manifest.js";
8
+ import { acquireSourceSnapshot, chooseSourceSnapshotStrategy, createSourceSnapshotContext, } from "./source-snapshot-adapters.js";
8
9
  import { getWorkspacePath } from "./workspace-layout.js";
9
10
  export function resolvePrepareSyncArtifacts(target, config, runId) {
10
11
  const dir = resolve(target, getWorkspacePath(config, "evidenceRoot"), "prepare", runId);
@@ -254,140 +255,119 @@ async function checkAuthEntry(entry, authStatePath, opts) {
254
255
  }
255
256
  }
256
257
  async function syncEntry(target, config, entry, opts) {
257
- const source = entry.source;
258
- const origin = inferSourceOrigin(source);
259
- const snapshot = resolveSourceSnapshot(config, entry);
260
- const snapshotPath = resolve(target, snapshot);
261
- const previousSnapshotHash = await hashIfExists(snapshotPath);
262
- const base = {
263
- envelopeVersion: 1,
264
- sourceKey: entry.id,
265
- sourcePath: entry.path,
266
- rawLane: entry.rawLane,
267
- provider: origin.provider,
268
- origin: origin.ref,
269
- snapshot,
270
- previousSnapshotHash,
258
+ const context = await createSourceSnapshotContext(target, config, entry, opts, hashIfExists);
259
+ return acquireSourceSnapshot(context, {
260
+ dryRun: skippedDryRunSnapshot,
261
+ local: syncLocalSnapshot,
262
+ provider: syncProviderSnapshot,
263
+ repository: syncRepositorySnapshot,
264
+ url: syncUrlSnapshot,
265
+ providerManaged: blockedProviderManagedSnapshot,
266
+ });
267
+ }
268
+ function skippedDryRunSnapshot(context) {
269
+ return {
270
+ ...context.base,
271
+ strategy: chooseSourceSnapshotStrategy(context.origin.method),
272
+ status: "skipped",
273
+ changed: false,
274
+ reason: "dry-run requested; no snapshot was written",
275
+ validation: { dryRun: "pass" },
271
276
  };
272
- if (opts.dryRun) {
273
- return {
274
- ...base,
275
- strategy: chooseStrategy(origin.method),
276
- status: "skipped",
277
- changed: false,
278
- reason: "dry-run requested; no snapshot was written",
279
- validation: { dryRun: "pass" },
280
- };
277
+ }
278
+ async function syncLocalSnapshot(context) {
279
+ const { base, entry, source, snapshotPath, previousSnapshotHash, target } = context;
280
+ const sourcePath = source.origin?.path ?? source.path;
281
+ if (!sourcePath) {
282
+ return sourceGap(base, "configured local origin has no path");
281
283
  }
282
- if (origin.method === "local") {
283
- const sourcePath = source.origin?.path ?? source.path;
284
- if (!sourcePath) {
285
- return sourceGap(base, "configured local origin has no path");
286
- }
287
- const originPath = resolve(target, sourcePath);
288
- if (!(await exists(originPath))) {
289
- return sourceGap(base, "configured local origin path does not exist");
290
- }
291
- await mkdir(dirname(snapshotPath), { recursive: true });
292
- if (shouldPreserveMachineReadable(source, originPath, snapshotPath)) {
293
- await copyFile(originPath, snapshotPath);
294
- }
295
- else {
296
- await writeFile(snapshotPath, await renderStructuredLocalMarkdown(target, entry, originPath), "utf8");
297
- }
298
- const snapshotHash = await sha256(snapshotPath);
284
+ const originPath = resolve(target, sourcePath);
285
+ if (!(await exists(originPath))) {
286
+ return sourceGap(base, "configured local origin path does not exist");
287
+ }
288
+ await mkdir(dirname(snapshotPath), { recursive: true });
289
+ if (shouldPreserveMachineReadable(source, originPath, snapshotPath)) {
290
+ await copyFile(originPath, snapshotPath);
291
+ }
292
+ else {
293
+ await writeFile(snapshotPath, await renderStructuredLocalMarkdown(target, entry, originPath), "utf8");
294
+ }
295
+ const snapshotHash = await sha256(snapshotPath);
296
+ return {
297
+ ...base,
298
+ strategy: "local-copy-structured-markdown",
299
+ status: "materialized",
300
+ snapshotHash,
301
+ changed: previousSnapshotHash !== snapshotHash,
302
+ reason: "local origin copied or materialized into configured snapshot",
303
+ validation: { sourceExists: "pass", bodyContent: "pass", snapshotWritten: "pass" },
304
+ };
305
+ }
306
+ async function syncProviderSnapshot(context, providerAdapter) {
307
+ const { target, config, entry, snapshotPath, previousSnapshotHash, opts, origin } = context;
308
+ const authStatePath = resolveSelectedAuthStatePath(target, config, opts);
309
+ const adapterResult = await runProviderAdapter(providerAdapter, target, entry, snapshotPath, previousSnapshotHash, authStatePath, opts);
310
+ if (!adapterResult)
311
+ return null;
312
+ if (adapterResult.status === "blocked-auth" && opts.browser && origin.method === "url") {
313
+ const browserResult = await fetchUrlSnapshotWithBrowser(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
299
314
  return {
300
- ...base,
301
- strategy: "local-copy-structured-markdown",
302
- status: "materialized",
303
- snapshotHash,
304
- changed: previousSnapshotHash !== snapshotHash,
305
- reason: "local origin copied or materialized into configured snapshot",
306
- validation: { sourceExists: "pass", bodyContent: "pass", snapshotWritten: "pass" },
315
+ ...browserResult,
316
+ adapter: `${providerAdapter}-browser-fallback`,
317
+ metadata: cleanMetadata({ providerAdapter, fallbackFrom: adapterResult.strategy }),
307
318
  };
308
319
  }
309
- const providerAdapter = selectProviderAdapter(source, origin);
310
- if (providerAdapter) {
311
- const authStatePath = resolveSelectedAuthStatePath(target, config, opts);
312
- const adapterResult = await runProviderAdapter(providerAdapter, target, entry, snapshotPath, previousSnapshotHash, authStatePath, opts);
313
- if (adapterResult) {
314
- if (adapterResult.status === "blocked-auth" && opts.browser && origin.method === "url") {
315
- const browserResult = await fetchUrlSnapshotWithBrowser(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
316
- return {
317
- ...browserResult,
318
- adapter: `${providerAdapter}-browser-fallback`,
319
- metadata: cleanMetadata({ providerAdapter, fallbackFrom: adapterResult.strategy }),
320
- };
321
- }
322
- return adapterResult;
323
- }
324
- }
325
- if (origin.method === "repository") {
326
- await mkdir(dirname(snapshotPath), { recursive: true });
327
- const content = renderRepositoryMetadata(entry, origin.ref ?? "");
328
- await writeFile(snapshotPath, content, "utf8");
329
- const snapshotHash = await sha256(snapshotPath);
320
+ return adapterResult;
321
+ }
322
+ async function syncRepositorySnapshot(context) {
323
+ const { base, entry, origin, snapshotPath, previousSnapshotHash } = context;
324
+ await mkdir(dirname(snapshotPath), { recursive: true });
325
+ const content = renderRepositoryMetadata(entry, origin.ref ?? "");
326
+ await writeFile(snapshotPath, content, "utf8");
327
+ const snapshotHash = await sha256(snapshotPath);
328
+ return {
329
+ ...base,
330
+ strategy: "repository-metadata-only",
331
+ status: "metadata-only",
332
+ snapshotHash,
333
+ changed: previousSnapshotHash !== snapshotHash,
334
+ reason: "repository origin recorded as metadata; configure contract paths or checkout policy before broad acquisition",
335
+ validation: { repositoryNotCrawled: "pass", snapshotWritten: "pass" },
336
+ };
337
+ }
338
+ async function syncUrlSnapshot(context) {
339
+ const { base, entry, source, origin, target, config, opts, snapshotPath, previousSnapshotHash } = context;
340
+ if (!opts.allowNetwork) {
330
341
  return {
331
342
  ...base,
332
- strategy: "repository-metadata-only",
333
- status: "metadata-only",
334
- snapshotHash,
335
- changed: previousSnapshotHash !== snapshotHash,
336
- reason: "repository origin recorded as metadata; configure contract paths or checkout policy before broad acquisition",
337
- validation: { repositoryNotCrawled: "pass", snapshotWritten: "pass" },
343
+ strategy: "playwright-auth-or-direct-fetch",
344
+ status: "blocked-auth",
345
+ changed: false,
346
+ reason: "remote URL requires --allow-network or a materialized snapshot; Playwright auth helper can create reusable storageState first",
347
+ validation: { networkAllowed: "fail", previousSnapshotPreserved: previousSnapshotHash ? "pass" : "skipped" },
338
348
  };
339
349
  }
340
- if (origin.method === "url") {
341
- if (!opts.allowNetwork) {
342
- return {
343
- ...base,
344
- strategy: "playwright-auth-or-direct-fetch",
345
- status: "blocked-auth",
346
- changed: false,
347
- reason: "remote URL requires --allow-network or a materialized snapshot; Playwright auth helper can create reusable storageState first",
348
- validation: { networkAllowed: "fail", previousSnapshotPreserved: previousSnapshotHash ? "pass" : "skipped" },
349
- };
350
- }
351
- const authStatePath = resolveSelectedAuthStatePath(target, config, opts);
352
- if (opts.browser) {
353
- if (origin.ref && await hasDirectRuntimeAuthForSource(source, origin.ref, authStatePath)) {
354
- const direct = await fetchUrlSnapshot(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
355
- if (direct.status === "materialized")
356
- return direct;
357
- }
358
- return fetchUrlSnapshotWithBrowser(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
350
+ const authStatePath = resolveSelectedAuthStatePath(target, config, opts);
351
+ if (opts.browser) {
352
+ if (origin.ref && await hasDirectRuntimeAuthForSource(source, origin.ref, authStatePath)) {
353
+ const direct = await fetchUrlSnapshot(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
354
+ if (direct.status === "materialized")
355
+ return direct;
359
356
  }
360
- return fetchUrlSnapshot(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
357
+ return fetchUrlSnapshotWithBrowser(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
361
358
  }
359
+ return fetchUrlSnapshot(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
360
+ }
361
+ function blockedProviderManagedSnapshot(context) {
362
362
  return {
363
- ...base,
363
+ ...context.base,
364
364
  strategy: "confirm-acquisition-method",
365
365
  status: "blocked-auth",
366
366
  changed: false,
367
367
  reason: "provider-managed origin needs an explicit acquisition method or exported snapshot",
368
- validation: { acquisitionMethodConfirmed: "fail", previousSnapshotPreserved: previousSnapshotHash ? "pass" : "skipped" },
368
+ validation: { acquisitionMethodConfirmed: "fail", previousSnapshotPreserved: context.previousSnapshotHash ? "pass" : "skipped" },
369
369
  };
370
370
  }
371
- function selectProviderAdapter(source, origin) {
372
- const keys = [
373
- origin.provider,
374
- origin.kind,
375
- source.origin?.provider,
376
- source.origin?.kind,
377
- source.kind,
378
- source.type,
379
- ].map((value) => normalizeAdapterKey(typeof value === "string" ? value : undefined));
380
- if (keys.some((value) => value.includes("confluence")))
381
- return "confluence";
382
- if (keys.some((value) => value.includes("jira")))
383
- return "jira";
384
- if (keys.some((value) => value.includes("gitlab")))
385
- return "gitlab";
386
- if (keys.some((value) => ["figma", "modao", "mastergo", "design", "design-asset", "design-source"].includes(value))) {
387
- return "design-source";
388
- }
389
- return undefined;
390
- }
391
371
  async function runProviderAdapter(adapter, target, entry, snapshotPath, previousSnapshotHash, authStatePath, opts) {
392
372
  if (adapter === "confluence") {
393
373
  return runConfluenceAdapter(target, entry, snapshotPath, previousSnapshotHash, authStatePath, opts);
@@ -560,10 +540,11 @@ async function runDesignAdapter(target, entry, snapshotPath, previousSnapshotHas
560
540
  });
561
541
  }
562
542
  const url = firstOriginString(entry.source, "apiUrl", "exportUrl", "url") ?? base.origin;
563
- if (!url)
564
- return null;
543
+ if (!url) {
544
+ return sourceGap(base, "design source needs origin.url, origin.apiUrl, origin.exportUrl, or a local exportPath/assetPath/filePath before acquisition", designProviderGapMetadata("missing-provider-locator"));
545
+ }
565
546
  if (!opts.allowNetwork) {
566
- return blockedAdapter(base, opts.browser ? "design-browser-render" : "design-api-or-browser", "design source requires --allow-network with token/export URL or --browser storageState acquisition");
547
+ return blockedAdapter(base, opts.browser ? "design-browser-render" : "design-api-or-browser", "design source requires --allow-network with token/export URL or --browser storageState acquisition", designProviderGapMetadata("network-not-allowed"));
567
548
  }
568
549
  const directRuntimeAuth = await hasDirectRuntimeAuthForSource(entry.source, url, authStatePath);
569
550
  if (opts.browser && !directRuntimeAuth) {
@@ -578,18 +559,18 @@ async function runDesignAdapter(target, entry, snapshotPath, previousSnapshotHas
578
559
  if (!fetched.ok) {
579
560
  if (opts.browser)
580
561
  return fetchUrlSnapshotWithBrowser(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
581
- return blockedAdapter(base, "design-api-or-export", fetched.reason);
562
+ return blockedAdapter(base, "design-api-or-export", fetched.reason, designProviderGapMetadata("provider-fetch-failed"));
582
563
  }
583
564
  if (looksLikeLoginPage(fetched.body, fetched.resolvedOrigin)) {
584
565
  if (opts.browser)
585
566
  return fetchUrlSnapshotWithBrowser(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
586
- return blockedAdapter(base, "design-api-or-export", "design adapter resolved to login/chrome content; previous snapshot was preserved");
567
+ return blockedAdapter(base, "design-api-or-export", "design adapter resolved to login/chrome content; previous snapshot was preserved", designProviderGapMetadata("provider-auth-blocked"));
587
568
  }
588
569
  const content = renderDesignMarkdown(fetched.body, fetched.resolvedOrigin, fetched.contentType);
589
570
  if (!hasMaterialContent(content)) {
590
571
  if (opts.browser)
591
572
  return fetchUrlSnapshotWithBrowser(target, entry, snapshotPath, previousSnapshotHash, authStatePath);
592
- return blockedAdapter(base, "design-api-or-export", "design adapter produced no material design content; previous snapshot was preserved");
573
+ return blockedAdapter(base, "design-api-or-export", "design adapter produced no material design content; previous snapshot was preserved", designProviderGapMetadata("provider-content-incomplete"));
593
574
  }
594
575
  return writeAdapterSnapshot(target, entry, snapshotPath, previousSnapshotHash, {
595
576
  adapter: "design-source",
@@ -1023,6 +1004,11 @@ function renderDesignMarkdown(body, origin, contentType) {
1023
1004
  "",
1024
1005
  fencedBody(JSON.stringify(parsed, null, 2), "application/json"),
1025
1006
  "",
1007
+ "## Reconstruction Guidance",
1008
+ "",
1009
+ "- Semantic category ownership, assets/raw files, reconstruction.json, and known gaps must be reviewed before treating this as complete UE reconstruction evidence.",
1010
+ "- If provider raw payloads or assets are unavailable, record a provider gap and fallback risk in downstream evidence.",
1011
+ "",
1026
1012
  ].filter((line) => typeof line === "string").join("\n");
1027
1013
  }
1028
1014
  function designNodeNames(value) {
@@ -1035,6 +1021,13 @@ function designNodeNames(value) {
1035
1021
  const nested = Array.isArray(children) ? children.flatMap(designNodeNames) : [];
1036
1022
  return current ? [current, ...nested] : nested;
1037
1023
  }
1024
+ function designProviderGapMetadata(reason) {
1025
+ return {
1026
+ providerGap: reason,
1027
+ fallbackEvidenceRequired: true,
1028
+ reconstructionRisk: "semantic UE reconstruction needs provider raw payload, assets/raw files, reconstruction.json, and known gaps before downstream design use",
1029
+ };
1030
+ }
1038
1031
  function parseCsvRows(value) {
1039
1032
  const rows = parseCsv(value).filter((row) => row.some((cell) => cell.trim().length > 0));
1040
1033
  const headers = rows.shift() ?? [];
@@ -1246,9 +1239,6 @@ function escapeTable(value) {
1246
1239
  function isRecord(value) {
1247
1240
  return typeof value === "object" && value !== null && !Array.isArray(value);
1248
1241
  }
1249
- function normalizeAdapterKey(value) {
1250
- return value?.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") ?? "";
1251
- }
1252
1242
  function renderRepositoryMetadata(entry, repository) {
1253
1243
  return [
1254
1244
  "---",
@@ -1317,15 +1307,6 @@ function shouldPreserveMachineReadable(source, originPath, snapshotPath) {
1317
1307
  [".json", ".yaml", ".yml"].includes(ext) ||
1318
1308
  [".json", ".yaml", ".yml"].includes(snapshotExt));
1319
1309
  }
1320
- function chooseStrategy(method) {
1321
- if (method === "local")
1322
- return "local-copy-structured-markdown";
1323
- if (method === "repository")
1324
- return "repository-metadata-only";
1325
- if (method === "url")
1326
- return "playwright-auth-or-direct-fetch";
1327
- return "confirm-acquisition-method";
1328
- }
1329
1310
  function renderDownstreamImpact(results) {
1330
1311
  const changed = results.filter((result) => result.changed);
1331
1312
  const blocked = results.filter((result) => ["blocked-auth", "source-gap", "failed"].includes(result.status));
@@ -1375,13 +1356,14 @@ function hasMaterialContent(value) {
1375
1356
  const body = value.replace(/^---[\s\S]*?---/, "").replace(/[#*\-[\]()`\s]/g, "");
1376
1357
  return body.length >= 20;
1377
1358
  }
1378
- function sourceGap(base, reason) {
1359
+ function sourceGap(base, reason, metadata) {
1379
1360
  return {
1380
1361
  ...base,
1381
1362
  strategy: "source-gap",
1382
1363
  status: "source-gap",
1383
1364
  changed: false,
1384
1365
  reason,
1366
+ metadata: cleanMetadata(metadata ?? {}),
1385
1367
  validation: { sourceAvailable: "fail" },
1386
1368
  };
1387
1369
  }
@@ -1395,13 +1377,14 @@ function blocked(base, reason) {
1395
1377
  validation: { bodyContent: "fail", previousSnapshotPreserved: base.previousSnapshotHash ? "pass" : "skipped" },
1396
1378
  };
1397
1379
  }
1398
- function blockedAdapter(base, strategy, reason) {
1380
+ function blockedAdapter(base, strategy, reason, metadata) {
1399
1381
  return {
1400
1382
  ...base,
1401
1383
  strategy,
1402
1384
  status: "blocked-auth",
1403
1385
  changed: false,
1404
1386
  reason,
1387
+ metadata: cleanMetadata(metadata ?? {}),
1405
1388
  validation: { bodyContent: "fail", previousSnapshotPreserved: base.previousSnapshotHash ? "pass" : "skipped" },
1406
1389
  };
1407
1390
  }