@khoavu882/doflow 2.0.0-beta.4 → 2.0.0-beta.5

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 (101) hide show
  1. package/core/.antigravity-plugin/plugin.json +0 -1
  2. package/core/.claude-plugin/plugin.json +1 -1
  3. package/core/.codex-plugin/plugin.json +1 -1
  4. package/core/.plugin/plugin.json +1 -1
  5. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  6. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  7. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  8. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  9. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  10. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  11. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  12. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  13. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  14. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  15. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  16. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  17. package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
  18. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  19. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
  20. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
  21. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
  22. package/core/harnesses/gemini/hooks/session-start.sh +5 -3
  23. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
  24. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
  25. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
  26. package/core/harnesses/kiro/hooks/session-start.sh +11 -5
  27. package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
  28. package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
  29. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
  30. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  31. package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
  32. package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
  33. package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
  34. package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
  35. package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
  36. package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
  37. package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
  38. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
  39. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
  40. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
  41. package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
  42. package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
  43. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
  44. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
  45. package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
  46. package/core/registry/assets.json +19 -0
  47. package/core/registry/contracts.json +2 -2
  48. package/core/registry/harnesses.json +196 -10
  49. package/core/registry/lifecycle.json +2626 -7
  50. package/core/registry/storage.json +24 -1
  51. package/core/shared/scripts/doflow/bin/doflow-run +10 -6
  52. package/core/shared/skills/do-design/SKILL.md +6 -6
  53. package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
  54. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
  55. package/core/shared/skills/do-implement/SKILL.md +3 -2
  56. package/core/shared/skills/do-plan/SKILL.md +7 -8
  57. package/package.json +1 -1
  58. package/src/adapters/antigravity/index.js +83 -47
  59. package/src/adapters/claude/index.js +15 -1
  60. package/src/adapters/codex/hooks.js +40 -5
  61. package/src/adapters/codex/index.js +52 -9
  62. package/src/adapters/copy-tree.js +4 -4
  63. package/src/adapters/gemini/hooks.js +57 -16
  64. package/src/adapters/gemini/index.js +57 -18
  65. package/src/adapters/kiro/index.js +15 -1
  66. package/src/adapters/pi/hooks.js +119 -0
  67. package/src/adapters/pi/index.js +34 -9
  68. package/src/cli/commands/install.js +1 -1
  69. package/src/cli/commands/update.js +1 -1
  70. package/src/cli/index.js +17 -5
  71. package/src/cli/runtime-commands.js +51 -12
  72. package/src/lifecycle/coverage.js +57 -0
  73. package/src/lifecycle/hook-step.js +61 -0
  74. package/src/lifecycle/index.js +28 -14
  75. package/src/registry/index.js +136 -7
  76. package/src/runtime/candidate.js +505 -0
  77. package/src/runtime/claims.js +14 -5
  78. package/src/runtime/cli-result.js +11 -4
  79. package/src/runtime/cli.js +35 -268
  80. package/src/runtime/context/budget.js +203 -0
  81. package/src/runtime/context/compose.js +1367 -0
  82. package/src/runtime/context/delivery-store.js +102 -0
  83. package/src/runtime/context/mandatory.js +594 -0
  84. package/src/runtime/context/pointer.js +142 -0
  85. package/src/runtime/context-pack.js +1029 -244
  86. package/src/runtime/evidence-fields.js +39 -0
  87. package/src/runtime/evidence-ledger.js +7 -0
  88. package/src/runtime/freshness.js +44 -7
  89. package/src/runtime/outcome.js +1 -1
  90. package/src/runtime/readiness.js +83 -13
  91. package/src/runtime/retrieval-plan.js +422 -73
  92. package/src/runtime/retrieval-record.js +223 -0
  93. package/src/runtime/scaffold/artifacts.js +3 -0
  94. package/src/runtime/source-version.js +289 -0
  95. package/src/runtime/task-attempts.js +1 -1
  96. package/src/runtime/task-scope.js +28 -3
  97. package/src/runtime/task-state.js +72 -7
  98. package/core/.antigravity-plugin/hooks.json +0 -39
  99. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  100. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  101. package/core/harnesses/shared/hooks/hooks.json +0 -47
@@ -83,6 +83,7 @@ function loadRegistry({ repoRoot, dir, fsImpl = fs }
83
83
  assets: loaded.assets.assets,
84
84
  mcp: loaded.mcp.servers,
85
85
  lifecycle: loaded.lifecycle.policies,
86
+ coverage: loaded.lifecycle.coverage ?? null,
86
87
  contracts: loaded.contracts.contracts,
87
88
  externalTools: loaded.externalTools.tools,
88
89
  modelProviders: loaded.models.providers,
@@ -132,11 +133,10 @@ function validateContract(value , location , errors , harnes
132
133
  if (new Set(list).size !== list.length) issue(errors, `${location}.${field}`, 'must not contain duplicates');
133
134
  }
134
135
  // An empty hookEvents used to be rejected outright, which assumed every harness wires at least one
135
- // hook. OpenCode and Pi break that: both implement lifecycle handlers as JS/TS code modules, so
136
- // DoFlow's shell hooks have nothing to attach to and it wires none. Forcing a non-empty list there
137
- // would mean declaring a hook DoFlow does not install — an inert claim of exactly the kind these
138
- // guards exist to remove. Empty is therefore allowed, but only with a note, so the distinction
139
- // between "wires nothing, deliberately" and "nobody filled this in" stays visible.
136
+ // hook. OpenCode has no DoFlow hook projection; Pi uses a code extension only for tool_call.
137
+ // Forcing a non-empty list for an unsupported event would declare a hook DoFlow does not install —
138
+ // an inert claim of exactly the kind these guards exist to remove. Empty is therefore allowed, but
139
+ // only with a note, so deliberate gaps stay distinct from missing registry data.
140
140
  const events = value.hookEvents;
141
141
  const noted = typeof value.note === 'string' && value.note.trim().length > 0;
142
142
  if (!Array.isArray(events) || events.some((item) => typeof item !== 'string' || !item)) {
@@ -412,6 +412,94 @@ function validateOrchestration(value , location , errors )
412
412
  }
413
413
  }
414
414
 
415
+ const CONTEXT_PROFILE_FIELDS = Object.freeze([
416
+ 'capacityTokens',
417
+ 'reserveTokens',
418
+ 'tokenTelemetry',
419
+ 'toolResultLimitBytes',
420
+ 'retention',
421
+ ]);
422
+
423
+ /** Validates one harness's `contextProfile` block (075 IC-013): capacity, reserve, telemetry,
424
+ * toolResultLimitBytes and retention. Each is a Fact<T> with {value, source, verifiedOn} or
425
+ * {unverified, verifiedOn}. */
426
+ function validateContextProfile(value , location , errors ) {
427
+ if (!object(value)) {
428
+ issue(errors, location, 'contextProfile block is required and must be an object');
429
+ return;
430
+ }
431
+ for (const field of CONTEXT_PROFILE_FIELDS) {
432
+ if (!Object.hasOwn(value, field)) {
433
+ issue(errors, location, `contextProfile is missing field '${field}'`);
434
+ }
435
+ }
436
+ for (const key of Object.keys(value)) {
437
+ if (!CONTEXT_PROFILE_FIELDS.includes(key)) {
438
+ issue(errors, location, `contextProfile has unknown key '${key}'`);
439
+ }
440
+ }
441
+ for (const field of CONTEXT_PROFILE_FIELDS) {
442
+ if (!Object.hasOwn(value, field)) continue;
443
+ const at = `${location} contextProfile '${field}'`;
444
+ const fact = value[field];
445
+ if (!object(fact)) {
446
+ issue(errors, at, 'must be a Fact object');
447
+ continue;
448
+ }
449
+ const hasUnverified = Object.hasOwn(fact, 'unverified');
450
+ const hasValue = Object.hasOwn(fact, 'value');
451
+
452
+ if (hasUnverified) {
453
+ if (typeof fact.unverified !== 'string' || !fact.unverified.trim()) {
454
+ issue(errors, at, 'unverified must be a non-empty string');
455
+ }
456
+ if (hasValue) {
457
+ issue(errors, at, 'cannot declare both value and unverified');
458
+ }
459
+ } else if (hasValue) {
460
+ if (typeof fact.source !== 'string' || !fact.source.trim()) {
461
+ issue(errors, at, 'a declared value requires a non-empty source');
462
+ }
463
+ } else {
464
+ issue(errors, at, 'requires either value with source or a non-empty unverified');
465
+ }
466
+
467
+ if (
468
+ typeof fact.verifiedOn !== 'string' ||
469
+ !/^\d{4}-\d{2}-\d{2}$/.test(fact.verifiedOn) ||
470
+ Number.isNaN(Date.parse(fact.verifiedOn)) ||
471
+ new Date(fact.verifiedOn).toISOString().slice(0, 10) !== fact.verifiedOn
472
+ ) {
473
+ issue(errors, at, 'requires verifiedOn as an ISO date (YYYY-MM-DD)');
474
+ }
475
+
476
+ if (hasValue && fact.value !== undefined) {
477
+ const val = fact.value;
478
+ if (field === 'capacityTokens') {
479
+ if (typeof val !== 'number' || !Number.isInteger(val) || val <= 0) {
480
+ issue(errors, at, 'value must be a positive integer');
481
+ }
482
+ } else if (field === 'reserveTokens') {
483
+ if (typeof val !== 'number' || !Number.isInteger(val) || val < 0) {
484
+ issue(errors, at, 'value must be a non-negative integer');
485
+ }
486
+ } else if (field === 'tokenTelemetry') {
487
+ if (val !== 'harness-reported' && val !== 'none') {
488
+ issue(errors, at, "value must be 'harness-reported' or 'none'");
489
+ }
490
+ } else if (field === 'toolResultLimitBytes') {
491
+ if (typeof val !== 'number' || !Number.isInteger(val) || val <= 0) {
492
+ issue(errors, at, 'value must be a positive integer');
493
+ }
494
+ } else if (field === 'retention') {
495
+ if (val !== 'supported' && val !== 'unsupported') {
496
+ issue(errors, at, "value must be 'supported' or 'unsupported'");
497
+ }
498
+ }
499
+ }
500
+ }
501
+ }
502
+
415
503
  function validateRegistry(registry , { repoRoot, fsImpl = fs } = {}) {
416
504
  const errors = [];
417
505
  const harnesses = Array.isArray(registry?.harnesses) ? registry.harnesses : null;
@@ -430,7 +518,7 @@ function validateRegistry(registry , { repoRoot, fsImpl = fs }
430
518
  idsUnique(mcp, 'mcp', errors); idsUnique(lifecycle, 'lifecycle', errors);
431
519
  idsUnique(externalTools, 'externalTools', errors);
432
520
 
433
- const harnessIds = new Set((harnesses || []).filter(object).map((item ) => item.id));
521
+ const harnessIds = new Set ((harnesses || []).filter(object).map((item ) => item.id ));
434
522
  for (const harness of harnesses || []) {
435
523
  const at = `harness '${harness?.id ?? '?'}'`;
436
524
  if (!object(harness)) { issue(errors, 'harnesses', 'entries must be objects'); continue; }
@@ -451,6 +539,7 @@ function validateRegistry(registry , { repoRoot, fsImpl = fs }
451
539
  validateCapability(declaration, `${at} capability '${capability}'`, errors);
452
540
  }
453
541
  validateOrchestration(harness.orchestration, at, errors);
542
+ validateContextProfile(harness.contextProfile, at, errors);
454
543
  if (harness.id === 'codex') validateNativeProjection(harness.nativeProjection, harness, { repoRoot, fsImpl }, errors);
455
544
  }
456
545
 
@@ -520,6 +609,8 @@ function validateRegistry(registry , { repoRoot, fsImpl = fs }
520
609
  if (mapping.status === 'unavailable' && typeof mapping.fallback !== 'string') issue(errors, at, `unavailable mapping '${harnessId}' requires fallback guidance`);
521
610
  }
522
611
  }
612
+
613
+ validateCoverage(registry?.coverage, registry?.lifecycle, harnessIds, errors);
523
614
  for (const tool of externalTools || []) validateExternalTool(tool, `external tool '${tool?.id ?? '?'}'`, errors);
524
615
 
525
616
  const modelProviders = Array.isArray(registry?.modelProviders) ? registry.modelProviders : null;
@@ -545,6 +636,44 @@ function validateRegistry(registry , { repoRoot, fsImpl = fs }
545
636
  return { ok: errors.length === 0, errors };
546
637
  }
547
638
 
639
+ const COVERAGE_CLASSES = new Set(['blocking', 'observation', 'advisory', 'unsupported', 'unknown']);
640
+ const COVERAGE_TIMINGS = new Set(['pre-tool', 'post-tool', 'session', 'prompt', 'stop', 'compact', 'subagent']);
641
+ const COVERAGE_PROFILES = new Set(['any', 'user-interactive', 'user-headless', 'project-interactive', 'project-headless', 'cli-extension', 'no-extensions', 'ide', 'cli-v3', 'cli-v3-agent', 'cli-2x-agent']);
642
+ const COVERAGE_KINDS = new Set(['doc', 'types', 'fixture', 'live']);
643
+
644
+ function validateCoverage(value , lifecycle , harnessIds , errors ) {
645
+ if (value === undefined || value === null) return;
646
+ const at = 'lifecycle.coverage';
647
+ if (!object(value)) { issue(errors, at, 'must be an object'); return; }
648
+ if (value.schemaVersion !== 1) issue(errors, `${at}.schemaVersion`, 'must be 1');
649
+ if (!Number.isInteger(value.revision) || value.revision < 1) issue(errors, `${at}.revision`, 'must be a positive integer');
650
+ if (typeof value.rowsDigest !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(value.rowsDigest)) issue(errors, `${at}.rowsDigest`, 'must be sha256 followed by 64 lowercase hex characters');
651
+ if (!object(value.refresh) || typeof value.refresh.trigger !== 'string' || !value.refresh.trigger.trim() || typeof value.refresh.owner !== 'string' || !value.refresh.owner.trim()) issue(errors, `${at}.refresh`, 'requires non-empty trigger and owner');
652
+ if (!object(value.profiles) || [...COVERAGE_PROFILES].some((profile) => typeof value.profiles?.[profile] !== 'string' || !value.profiles[profile].trim())) issue(errors, `${at}.profiles`, 'must define every supported profile with a non-empty meaning');
653
+ if (!Array.isArray(value.rows)) { issue(errors, `${at}.rows`, 'must be an array'); return; }
654
+ const keys = new Set ();
655
+ for (const [index, row] of value.rows.entries()) {
656
+ const rowAt = `${at}.rows[${index}]`;
657
+ if (!object(row)) { issue(errors, rowAt, 'must be an object'); continue; }
658
+ for (const field of ['policy', 'harness', 'event']) if (typeof row[field] !== 'string' || !row[field]) issue(errors, `${rowAt}.${field}`, 'must be a non-empty string');
659
+ if (row.policy !== '*' && !(lifecycle || []).some((policy ) => policy.id === row.policy)) issue(errors, `${rowAt}.policy`, 'must name a lifecycle policy or *');
660
+ if (!harnessIds.has(row.harness)) issue(errors, `${rowAt}.harness`, 'must name a registry harness');
661
+ if (!COVERAGE_TIMINGS.has(row.timing)) issue(errors, `${rowAt}.timing`, 'has an invalid timing');
662
+ if (!COVERAGE_PROFILES.has(row.profile)) issue(errors, `${rowAt}.profile`, 'has an invalid profile');
663
+ if (typeof row.wired !== 'boolean') issue(errors, `${rowAt}.wired`, 'must be a boolean');
664
+ if (row.configSource !== null && (typeof row.configSource !== 'string' || !row.configSource)) issue(errors, `${rowAt}.configSource`, 'must be a path:line or null');
665
+ if (row.wired && typeof row.configSource !== 'string') issue(errors, `${rowAt}.configSource`, 'is required when wired');
666
+ if (!object(row.harnessVersion) || typeof row.harnessVersion.tested !== 'string' || !row.harnessVersion.tested || (row.harnessVersion.range !== undefined && typeof row.harnessVersion.range !== 'string')) issue(errors, `${rowAt}.harnessVersion`, 'requires tested and optional range strings');
667
+ if (!COVERAGE_CLASSES.has(row.class)) issue(errors, `${rowAt}.class`, 'has an invalid class');
668
+ if (!['verified', 'unverified'].includes(row.verification)) issue(errors, `${rowAt}.verification`, 'must be verified or unverified');
669
+ for (const field of ['prerequisites', 'covers', 'doesNotCover', 'caveats']) if (!Array.isArray(row[field]) || row[field].some((item ) => typeof item !== 'string')) issue(errors, `${rowAt}.${field}`, 'must be an array of strings');
670
+ if (!Array.isArray(row.provenance) || row.provenance.some((item ) => !object(item) || !COVERAGE_KINDS.has(item.kind) || typeof item.locator !== 'string' || !item.locator || (['doc', 'types', 'live'].includes(item.kind) && (typeof item.date !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(item.date))))) issue(errors, `${rowAt}.provenance`, 'contains malformed provenance');
671
+ const key = JSON.stringify([row.policy, row.harness, row.event, row.profile]);
672
+ if (keys.has(key)) issue(errors, rowAt, 'duplicates a policy/harness/event/profile tuple');
673
+ keys.add(key);
674
+ }
675
+ }
676
+
548
677
  function harnessFor(registry , id ) {
549
678
  const harness = registry.harnesses.find((item ) => item.id === id);
550
679
  if (!harness) throw new Error(`Unknown registry harness '${id}'`);
@@ -605,7 +734,7 @@ function capabilityMapData(registry ) {
605
734
  }
606
735
 
607
736
  module.exports = {
608
- REGISTRY_FILES, CAPABILITY_STATUS, ORCHESTRATION_STATUS, ORCHESTRATION_OPS, validateOrchestration, PROVIDER_SOURCE_MODES, validateCapabilityProviders, EXTERNAL_TOOL_IDS, EXTERNAL_TOOL_ACTIONS,
737
+ REGISTRY_FILES, CAPABILITY_STATUS, ORCHESTRATION_STATUS, ORCHESTRATION_OPS, validateOrchestration, CONTEXT_PROFILE_FIELDS, validateContextProfile, PROVIDER_SOURCE_MODES, validateCapabilityProviders, EXTERNAL_TOOL_IDS, EXTERNAL_TOOL_ACTIONS, validateCoverage,
609
738
  parseRegistryFile, registryDir, loadRegistry, validateRegistry,
610
739
  harnessFor, selectAssets, selectMcpServers, mcpCapable, nativeMcpCatalog, capabilityMapData,
611
740
  };