@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.
- package/core/.antigravity-plugin/plugin.json +0 -1
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
- package/core/harnesses/gemini/hooks/session-start.sh +5 -3
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
- package/core/harnesses/kiro/hooks/session-start.sh +11 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
- package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
- package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
- package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
- package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
- package/core/registry/assets.json +19 -0
- package/core/registry/contracts.json +2 -2
- package/core/registry/harnesses.json +196 -10
- package/core/registry/lifecycle.json +2626 -7
- package/core/registry/storage.json +24 -1
- package/core/shared/scripts/doflow/bin/doflow-run +10 -6
- package/core/shared/skills/do-design/SKILL.md +6 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
- package/core/shared/skills/do-implement/SKILL.md +3 -2
- package/core/shared/skills/do-plan/SKILL.md +7 -8
- package/package.json +1 -1
- package/src/adapters/antigravity/index.js +83 -47
- package/src/adapters/claude/index.js +15 -1
- package/src/adapters/codex/hooks.js +40 -5
- package/src/adapters/codex/index.js +52 -9
- package/src/adapters/copy-tree.js +4 -4
- package/src/adapters/gemini/hooks.js +57 -16
- package/src/adapters/gemini/index.js +57 -18
- package/src/adapters/kiro/index.js +15 -1
- package/src/adapters/pi/hooks.js +119 -0
- package/src/adapters/pi/index.js +34 -9
- package/src/cli/commands/install.js +1 -1
- package/src/cli/commands/update.js +1 -1
- package/src/cli/index.js +17 -5
- package/src/cli/runtime-commands.js +51 -12
- package/src/lifecycle/coverage.js +57 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/index.js +28 -14
- package/src/registry/index.js +136 -7
- package/src/runtime/candidate.js +505 -0
- package/src/runtime/claims.js +14 -5
- package/src/runtime/cli-result.js +11 -4
- package/src/runtime/cli.js +35 -268
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +102 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context-pack.js +1029 -244
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/evidence-ledger.js +7 -0
- package/src/runtime/freshness.js +44 -7
- package/src/runtime/outcome.js +1 -1
- package/src/runtime/readiness.js +83 -13
- package/src/runtime/retrieval-plan.js +422 -73
- package/src/runtime/retrieval-record.js +223 -0
- package/src/runtime/scaffold/artifacts.js +3 -0
- package/src/runtime/source-version.js +289 -0
- package/src/runtime/task-attempts.js +1 -1
- package/src/runtime/task-scope.js +28 -3
- package/src/runtime/task-state.js +72 -7
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
package/src/registry/index.js
CHANGED
|
@@ -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
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
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
|
};
|