harness-everything 0.30.9 → 0.31.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +34 -0
- package/README.md +8 -9
- package/VERIFICATION.md +1 -1
- package/benchmarks/results/live-host/claude-2026-10-08-issue-297/README.md +27 -0
- package/benchmarks/results/live-host/codex-2026-10-13/README.md +55 -0
- package/benchmarks/results/live-host/codex-2026-10-13/observation-records.json +88 -0
- package/benchmarks/results/pr-303-review-2026-10-13/README.md +81 -0
- package/benchmarks/results/pr-303-review-2026-10-13/ci/run.json +1330 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/mechanism-35-clean-env.log +1 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/mechanism-35-inherited-flag.log +34 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate-captured.json +46 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate-diagnosis.md +13 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate.json +46 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/summary.json +57 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-collision.log +8 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-consistency.log +624 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-docs-capabilities.log +131 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-platform-compatibility.log +9 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-plugin-openai.log +6 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-plugin-submission.log +5 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-references.log +6 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-release.log +73 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-repo-contract.log +5 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-routing-invariants.log +785 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-routing-skills.log +44 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/summary.json +12 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/test-consistency.log +624 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/test-release.log +73 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/summary.json +79 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test-mechanism.log +1737 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test-mutations.log +16 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test.log +3297 -0
- package/benchmarks/results/pr-303-review-2026-10-13/manifest.json +319 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/binding-resolution-green.log +36 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/determinism.json +174 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/quality-evidence.json +314 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/quality-report.json +638 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/red.log +240 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/untyped-fable-probe.json +22 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/workflow-obligations-green.log +254 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/mechanism-35-clean-env-fixed.log +1 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/mechanism-35-inherited-flag-fixed.log +1 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.meta.json +8 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.stderr.log +90 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.stdout.log +3186 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/verify-gate-final.json +46 -0
- package/bin/cli.js +1 -1
- package/ci/doc-capability-consistency.test.js +11 -1
- package/ci/invariant-routing.test.js +7 -4
- package/ci/mechanism-2ag-router-worktree-contract.test.js +2 -2
- package/ci/mechanism-2ai-workspace-fingerprint-batch.test.js +2 -1
- package/ci/mechanism-2al-codex-hooks-compat.test.js +19 -0
- package/ci/mechanism-2am-observations-session-import.test.js +221 -0
- package/ci/mechanism-2an-workflow-obligations.test.js +352 -3
- package/ci/mechanism-2ao-plugin-sync.test.js +129 -4
- package/ci/mechanism-2aq-fable-stage-bindings.test.js +168 -0
- package/ci/mechanism-2ar-binding-resolution.test.js +211 -0
- package/ci/mechanism-2w-platform-compatibility.test.js +12 -0
- package/ci/mechanism-34-observations.test.js +64 -0
- package/ci/mechanism-35-verification-contract.test.js +1 -1
- package/ci/mutation-check.js +2 -0
- package/ci/mutation-loader.js +4 -0
- package/ci/openai-plugin-package.test.js +7 -5
- package/ci/prompt-contract-regressions.test.js +7 -6
- package/ci/router-workflow-plan.test.js +109 -14
- package/ci/skill-routing-check.js +44 -15
- package/ci/workflow-plan-consumers.test.js +41 -1
- package/docs/architecture.md +10 -10
- package/docs/enforcement-locks.md +1 -1
- package/docs/mechanism-first-skill-mesh.md +12 -11
- package/docs/openai-plugin.md +1 -1
- package/docs/philosophy.md +7 -6
- package/docs/platform-capabilities.md +14 -12
- package/docs/platform-compatibility.json +5 -5
- package/docs/routing.md +16 -17
- package/docs/system-one-observations.md +36 -9
- package/docs/system-one-routing.md +10 -7
- package/docs/system-one-suggestion-gates.md +5 -5
- package/docs/workflow-runtime.md +6 -2
- package/docs/workflows/harness-everything.md +14 -14
- package/evals/harness-everything/tasks/positive-tier1.yaml +1 -1
- package/evals/harness-everything/tasks/positive-tier2.yaml +1 -1
- package/evals/harness-everything/tasks/positive-tier3.yaml +1 -1
- package/fable-mode/CONTRACT-FORMAT.md +4 -1
- package/fable-mode/SKILL.md +6 -8
- package/fable-mode/agents/fable-orchestrator.md +10 -3
- package/fable-mode/execution-guardrails/SKILL.md +1 -1
- package/fable-mode/fable-haiku/SKILL.md +1 -1
- package/fable-mode/fable-opus/SKILL.md +1 -1
- package/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/fable-mode/scripts/workflow-plan-consumer.js +101 -2
- package/harness-everything/SKILL.md +8 -7
- package/harness-everything/references/router-workflow-plan.md +11 -6
- package/harness-everything/references/triage-and-tiers.md +21 -23
- package/harness-everything/schemas/router-workflow-plan.schema.json +2 -0
- package/harness-everything/scripts/kernel-router-core.js +52 -55
- package/harness-everything/scripts/kernel-router.js +48 -5
- package/harness-everything/scripts/router-contract-core.js +7 -7
- package/harness-everything/scripts/router-contract.js +1 -3
- package/harness-everything/scripts/routing-keywords.json +417 -121
- package/harness-everything/scripts/tier-router.js +64 -113
- package/hooks/scripts/contract-test.js +29 -3
- package/hooks/scripts/lib/binding-resolver.js +76 -0
- package/hooks/scripts/lib/fable-contracts.js +1 -1
- package/hooks/scripts/lib/observations.js +88 -12
- package/hooks/scripts/lib/workflow-obligations.js +292 -7
- package/hooks/scripts/lib/workflow-runtime.js +8 -1
- package/hooks/scripts/workflow-disposition.js +116 -2
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/contract-test.js +29 -3
- package/plugins/harness-everything/hooks/scripts/lib/binding-resolver.js +76 -0
- package/plugins/harness-everything/hooks/scripts/lib/fable-contracts.js +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/observations.js +88 -12
- package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +292 -7
- package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +8 -1
- package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +116 -2
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/scripts/lib/advisory-text.js +2 -2
- package/plugins/harness-everything/scripts/lib/generated-skills.js +78 -0
- package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +4 -1
- package/plugins/harness-everything/skills/fable-mode/SKILL.md +6 -8
- package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +10 -3
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +101 -2
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +8 -7
- package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md +11 -6
- package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +21 -23
- package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +2 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router-core.js +52 -55
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +48 -5
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +7 -7
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +1 -3
- package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +417 -121
- package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +64 -113
- package/sbom.cdx.json +7 -7
- package/scripts/codex-hooks-compat.js +39 -14
- package/scripts/lib/advisory-text.js +2 -2
- package/scripts/lib/generated-skills.js +78 -0
- package/scripts/plugin-sync.js +100 -29
- package/scripts/system-one-observations-export.js +29 -2
- package/scripts/system-one-observations-import-codex-sessions.js +307 -0
- package/scripts/system-one-observations-review.js +11 -1
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { atomicWriteJson, readJson } = require('./fable-contracts');
|
|
5
|
+
const { resolveBinding, PACKAGED_BINDING_PATHS: BINDING_PATHS } = require('./binding-resolver');
|
|
5
6
|
|
|
6
7
|
const OBLIGATION_SCHEMA_VERSION = 1;
|
|
7
8
|
const OBLIGATION_FILE = 'workflow-obligations.json';
|
|
8
9
|
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
9
10
|
const ALLOWED_ESCAPE_REASONS = new Set(['workflow-uncovered-scope', 'host-capability-unavailable']);
|
|
11
|
+
const STEP_TYPES = new Set(['review', 'behavior-change', 'verification', 'commit']);
|
|
12
|
+
const REQUIRED_BINDINGS_BY_STEP = {
|
|
13
|
+
review: [],
|
|
14
|
+
'behavior-change': ['tdd'],
|
|
15
|
+
verification: ['verification-loop'],
|
|
16
|
+
commit: ['git-commit'],
|
|
17
|
+
};
|
|
10
18
|
|
|
11
19
|
function obligationPath(sessionDir) {
|
|
12
20
|
return path.join(sessionDir, OBLIGATION_FILE);
|
|
@@ -53,7 +61,7 @@ function planningObligations() {
|
|
|
53
61
|
];
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
function executionObligations(plan) {
|
|
64
|
+
function executionObligations(plan, stepBindingsEnabled = false) {
|
|
57
65
|
return [
|
|
58
66
|
{
|
|
59
67
|
id: 'execute',
|
|
@@ -61,7 +69,7 @@ function executionObligations(plan) {
|
|
|
61
69
|
requirement: 'Resolve the planned requirement fragments using adaptive Skill/direct-action bindings.',
|
|
62
70
|
required: true,
|
|
63
71
|
escapable: true,
|
|
64
|
-
suggestedSkills: suggestedBindings(plan, 'execution'),
|
|
72
|
+
suggestedSkills: stepBindingsEnabled ? [] : suggestedBindings(plan, 'execution'),
|
|
65
73
|
},
|
|
66
74
|
{
|
|
67
75
|
id: 'verify',
|
|
@@ -69,7 +77,7 @@ function executionObligations(plan) {
|
|
|
69
77
|
requirement: 'Produce objective verification evidence appropriate to the completed requirements before claiming completion.',
|
|
70
78
|
required: true,
|
|
71
79
|
escapable: false,
|
|
72
|
-
suggestedSkills: suggestedBindings(plan, 'verification'),
|
|
80
|
+
suggestedSkills: stepBindingsEnabled ? [] : suggestedBindings(plan, 'verification'),
|
|
73
81
|
},
|
|
74
82
|
];
|
|
75
83
|
}
|
|
@@ -117,7 +125,7 @@ function initializePlanningContract(context, plan) {
|
|
|
117
125
|
return set;
|
|
118
126
|
}
|
|
119
127
|
|
|
120
|
-
function parseRequirements(requirementsJson) {
|
|
128
|
+
function parseRequirements(requirementsJson, workspaceRoot) {
|
|
121
129
|
let parsed;
|
|
122
130
|
try { parsed = JSON.parse(String(requirementsJson || '')); }
|
|
123
131
|
catch (_) { throw new Error('--requirements-json must be valid JSON'); }
|
|
@@ -136,15 +144,135 @@ function parseRequirements(requirementsJson) {
|
|
|
136
144
|
const suggestedSkills = Array.isArray(item.suggestedSkills)
|
|
137
145
|
? [...new Set(item.suggestedSkills.filter(value => typeof value === 'string' && value.trim()).map(value => value.trim()))]
|
|
138
146
|
: [];
|
|
139
|
-
|
|
147
|
+
const rawStepType = item.stepType === undefined ? '' : String(item.stepType).trim();
|
|
148
|
+
if (rawStepType && !STEP_TYPES.has(rawStepType)) throw new Error(`unsupported requirement stepType: ${rawStepType}`);
|
|
149
|
+
for (const field of ['requiredBindings', 'optionalBindings']) {
|
|
150
|
+
if (Object.prototype.hasOwnProperty.call(item, field) && !Array.isArray(item[field])) {
|
|
151
|
+
throw new Error(`${id}.${field} must be an array`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const requirement = { id, order: index + 1, summary, acceptance, suggestedSkills };
|
|
155
|
+
if (!rawStepType) return requirement;
|
|
156
|
+
|
|
157
|
+
const requiredBindings = normalizeBindingDeclarations([
|
|
158
|
+
...(REQUIRED_BINDINGS_BY_STEP[rawStepType] || []),
|
|
159
|
+
...(item.requiredBindings || []),
|
|
160
|
+
]);
|
|
161
|
+
const requiredIds = new Set(requiredBindings.map(binding => binding.id));
|
|
162
|
+
const optionalBindings = normalizeBindingDeclarations([
|
|
163
|
+
...(item.optionalBindings || []),
|
|
164
|
+
...suggestedSkills,
|
|
165
|
+
]).filter(binding => !requiredIds.has(binding.id));
|
|
166
|
+
if (rawStepType === 'review' && [...requiredBindings, ...optionalBindings].some(binding => binding.id === 'tdd')) {
|
|
167
|
+
throw new Error('review steps cannot bind tdd');
|
|
168
|
+
}
|
|
169
|
+
requirement.stepType = rawStepType;
|
|
170
|
+
requirement.status = 'pending';
|
|
171
|
+
requirement.evidence = null;
|
|
172
|
+
requirement.reasonCode = null;
|
|
173
|
+
requirement.updatedAt = null;
|
|
174
|
+
requirement.requiredBindings = requiredBindings.map(binding => newBinding(binding.id, true, binding.path, workspaceRoot));
|
|
175
|
+
requirement.optionalBindings = optionalBindings.map(binding => newBinding(binding.id, false, binding.path, workspaceRoot));
|
|
176
|
+
return requirement;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function normalizeBindingPath(value) {
|
|
181
|
+
if (typeof value !== 'string' || !value.trim()) throw new Error('binding paths must be non-empty repository-relative strings');
|
|
182
|
+
const normalized = value.trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/g, '');
|
|
183
|
+
const parts = normalized.split('/');
|
|
184
|
+
if (!normalized || normalized.startsWith('/') || /^[A-Za-z]:/.test(normalized) ||
|
|
185
|
+
parts.some(part => !part || part === '.' || part === '..') || /[*?\[\]]/.test(normalized)) {
|
|
186
|
+
throw new Error(`binding path must be a concrete repository-relative path: ${value}`);
|
|
187
|
+
}
|
|
188
|
+
return normalized;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function normalizeBindingDeclarations(values) {
|
|
192
|
+
const result = [];
|
|
193
|
+
const byId = new Map();
|
|
194
|
+
for (const value of values || []) {
|
|
195
|
+
const bindingId = typeof value === 'string' ? value.trim()
|
|
196
|
+
: value && typeof value === 'object' && typeof value.id === 'string' ? value.id.trim() : '';
|
|
197
|
+
if (!SAFE_ID.test(bindingId)) throw new Error('binding ids must be stable identifier strings');
|
|
198
|
+
const bindingPath = value && typeof value === 'object' && value.path !== undefined && value.path !== null
|
|
199
|
+
? normalizeBindingPath(value.path)
|
|
200
|
+
: null;
|
|
201
|
+
if (byId.has(bindingId)) {
|
|
202
|
+
const existing = byId.get(bindingId);
|
|
203
|
+
if (existing.path && bindingPath && existing.path !== bindingPath) throw new Error(`binding ${bindingId} declares conflicting paths`);
|
|
204
|
+
if (!existing.path && bindingPath) existing.path = bindingPath;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const declaration = { id: bindingId, path: bindingPath };
|
|
208
|
+
byId.set(bindingId, declaration);
|
|
209
|
+
result.push(declaration);
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function newBinding(id, required, declaredPath = null, workspaceRoot = null) {
|
|
215
|
+
if (declaredPath && BINDING_PATHS[id] && declaredPath !== BINDING_PATHS[id]) {
|
|
216
|
+
throw new Error(`binding ${id} must use its registered path ${BINDING_PATHS[id]}`);
|
|
217
|
+
}
|
|
218
|
+
const bindingPath = declaredPath || BINDING_PATHS[id] || null;
|
|
219
|
+
const resolution = resolveBinding({ id, path: bindingPath }, workspaceRoot);
|
|
220
|
+
return {
|
|
221
|
+
id,
|
|
222
|
+
required,
|
|
223
|
+
status: 'pending',
|
|
224
|
+
path: bindingPath,
|
|
225
|
+
availability: resolution.availability,
|
|
226
|
+
resolvedPath: resolution.file,
|
|
227
|
+
evidence: null,
|
|
228
|
+
reasonCode: null,
|
|
229
|
+
updatedAt: null,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function activeStepRecord(set) {
|
|
234
|
+
if (!set?.stepBindingsEnabled) return null;
|
|
235
|
+
return set.requirements.find(item => item.stepType && item.status !== 'pass') || null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function activeStepSnapshot(set) {
|
|
239
|
+
const step = activeStepRecord(set);
|
|
240
|
+
if (!step) return null;
|
|
241
|
+
const visibleBinding = binding => ({
|
|
242
|
+
id: binding.id,
|
|
243
|
+
status: binding.status,
|
|
244
|
+
availability: binding.availability,
|
|
245
|
+
path: binding.path,
|
|
246
|
+
resolvedPath: binding.resolvedPath || null,
|
|
140
247
|
});
|
|
248
|
+
return {
|
|
249
|
+
id: step.id,
|
|
250
|
+
order: step.order,
|
|
251
|
+
stepType: step.stepType,
|
|
252
|
+
summary: step.summary,
|
|
253
|
+
completionCriteria: step.acceptance,
|
|
254
|
+
status: step.status,
|
|
255
|
+
requiredBindings: step.requiredBindings.map(visibleBinding),
|
|
256
|
+
optionalBindings: step.optionalBindings.map(visibleBinding),
|
|
257
|
+
};
|
|
141
258
|
}
|
|
142
259
|
|
|
143
260
|
function recordPlanning(context, requirementsJson, selectedPlan, requestedStrategy, evidence) {
|
|
144
261
|
if (!needsPlanningContract(selectedPlan)) throw new Error('selected workflow does not require a planning contract');
|
|
145
262
|
const loaded = loadObligationSet(context, selectedPlan);
|
|
146
263
|
if (!loaded.set) throw new Error('workflow planning contract unavailable or invalid: ' + loaded.error);
|
|
147
|
-
const requirements = parseRequirements(requirementsJson);
|
|
264
|
+
const requirements = parseRequirements(requirementsJson, context.root);
|
|
265
|
+
const hasTypedSteps = requirements.some(item => item.stepType);
|
|
266
|
+
if (hasTypedSteps && requirements.some(item => !item.stepType)) {
|
|
267
|
+
throw new Error('all requirement fragments must declare stepType when step bindings are used');
|
|
268
|
+
}
|
|
269
|
+
const isFable = String(selectedPlan.strategy || '').startsWith('fable-');
|
|
270
|
+
if (!isFable && !hasTypedSteps) {
|
|
271
|
+
throw new Error('selected single-agent workflows require ordered stepType values: review, behavior-change, verification, or commit');
|
|
272
|
+
}
|
|
273
|
+
if (!isFable && !requirements.some(item => item.stepType === 'verification')) {
|
|
274
|
+
throw new Error('step-bound workflows require an explicit verification step');
|
|
275
|
+
}
|
|
148
276
|
const requested = String(requestedStrategy || '').trim();
|
|
149
277
|
const planningEvidence = String(evidence || '').trim();
|
|
150
278
|
if (!requested) throw new Error('--strategy is required');
|
|
@@ -152,6 +280,8 @@ function recordPlanning(context, requirementsJson, selectedPlan, requestedStrate
|
|
|
152
280
|
|
|
153
281
|
const now = new Date().toISOString();
|
|
154
282
|
loaded.set.requirements = requirements;
|
|
283
|
+
loaded.set.stepBindingsEnabled = hasTypedSteps && !isFable;
|
|
284
|
+
loaded.set.activeStepId = null;
|
|
155
285
|
const decompose = loaded.set.obligations.find(item => item.id === 'decompose');
|
|
156
286
|
if (decompose) {
|
|
157
287
|
decompose.status = 'pass';
|
|
@@ -202,12 +332,18 @@ function materializeExecutionObligations(context, plan) {
|
|
|
202
332
|
if (!loaded.set) throw new Error('workflow planning contract unavailable or invalid: ' + loaded.error);
|
|
203
333
|
if (planningUnresolved(context, plan).length) throw new Error('requirements/workflow planning must be resolved before start');
|
|
204
334
|
const existing = new Map(loaded.set.obligations.map(item => [item.id, item]));
|
|
205
|
-
for (const item of executionObligations(plan)) {
|
|
335
|
+
for (const item of executionObligations(plan, loaded.set.stepBindingsEnabled === true)) {
|
|
206
336
|
if (!existing.has(item.id)) loaded.set.obligations.push(newObligation(item));
|
|
337
|
+
else if (loaded.set.stepBindingsEnabled === true) {
|
|
338
|
+
const current = existing.get(item.id);
|
|
339
|
+
current.suggestedSkills = [];
|
|
340
|
+
}
|
|
207
341
|
}
|
|
342
|
+
syncTypedAggregateObligations(loaded.set);
|
|
208
343
|
loaded.set.phase = 'execution';
|
|
209
344
|
loaded.set.activeStrategy = plan.strategy;
|
|
210
345
|
loaded.set.revision = context.workflow.revision;
|
|
346
|
+
loaded.set.activeStepId = activeStepRecord(loaded.set)?.id || null;
|
|
211
347
|
atomicWriteJson(obligationPath(context.sessionDir), loaded.set);
|
|
212
348
|
return loaded.set;
|
|
213
349
|
}
|
|
@@ -223,6 +359,30 @@ function validateObligationSet(context, set, plan = context.workflow?.pendingPla
|
|
|
223
359
|
ids.add(obligation.id);
|
|
224
360
|
if (!['pending', 'pass', 'escaped', 'blocked'].includes(obligation.status)) return 'obligation-status-invalid';
|
|
225
361
|
}
|
|
362
|
+
if (set.stepBindingsEnabled === true) {
|
|
363
|
+
if (set.requirements.some(item => !item || !STEP_TYPES.has(item.stepType) ||
|
|
364
|
+
!['pending', 'pass', 'blocked'].includes(item.status) || !Array.isArray(item.requiredBindings) ||
|
|
365
|
+
!Array.isArray(item.optionalBindings))) return 'steps-invalid';
|
|
366
|
+
for (const step of set.requirements) {
|
|
367
|
+
for (const binding of [...step.requiredBindings, ...step.optionalBindings]) {
|
|
368
|
+
if (!binding || !SAFE_ID.test(binding.id || '') || !['pending', 'loaded', 'not-needed', 'unavailable'].includes(binding.status)) {
|
|
369
|
+
return 'step-binding-invalid';
|
|
370
|
+
}
|
|
371
|
+
if (binding.path !== null && typeof binding.path === 'string') {
|
|
372
|
+
try { if (normalizeBindingPath(binding.path) !== binding.path) return 'step-binding-path-invalid'; }
|
|
373
|
+
catch (_) { return 'step-binding-path-invalid'; }
|
|
374
|
+
} else if (binding.path !== null) return 'step-binding-path-invalid';
|
|
375
|
+
if (binding.resolvedPath !== undefined && binding.resolvedPath !== null && typeof binding.resolvedPath !== 'string') return 'step-binding-path-invalid';
|
|
376
|
+
if (binding.status === 'loaded' && !binding.path && !binding.resolvedPath) return 'step-binding-unknown-loaded';
|
|
377
|
+
}
|
|
378
|
+
if (step.status === 'pass' && (!step.evidence ||
|
|
379
|
+
step.requiredBindings.some(binding => binding.status !== 'loaded') ||
|
|
380
|
+
step.optionalBindings.some(binding => !['loaded', 'not-needed'].includes(binding.status)))) {
|
|
381
|
+
return 'step-resolution-invalid';
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (set.phase === 'execution' && set.activeStepId !== (activeStepRecord(set)?.id || null)) return 'active-step-invalid';
|
|
385
|
+
}
|
|
226
386
|
return null;
|
|
227
387
|
}
|
|
228
388
|
|
|
@@ -278,6 +438,113 @@ function updateObligation(context, obligationId, disposition, options = {}) {
|
|
|
278
438
|
return obligation;
|
|
279
439
|
}
|
|
280
440
|
|
|
441
|
+
function updateBinding(context, stepId, bindingId, disposition, options = {}) {
|
|
442
|
+
if (!SAFE_ID.test(stepId || '') || !SAFE_ID.test(bindingId || '')) throw new Error('step and binding ids must be stable identifiers');
|
|
443
|
+
if (!['loaded', 'not-needed', 'unavailable'].includes(disposition)) {
|
|
444
|
+
throw new Error('--disposition must be loaded, not-needed, or unavailable');
|
|
445
|
+
}
|
|
446
|
+
const plan = context.workflow?.pendingPlan || context.workflow?.workflowPlan;
|
|
447
|
+
const loaded = loadObligationSet(context, plan);
|
|
448
|
+
if (!loaded.set) throw new Error('workflow obligation contract unavailable or invalid: ' + loaded.error);
|
|
449
|
+
if (!loaded.set.stepBindingsEnabled) throw new Error('workflow has no active-step binding contract');
|
|
450
|
+
const step = activeStepRecord(loaded.set);
|
|
451
|
+
if (!step || step.id !== stepId) throw new Error('bindings may only be resolved for the active step');
|
|
452
|
+
const binding = [...step.requiredBindings, ...step.optionalBindings].find(item => item.id === bindingId);
|
|
453
|
+
if (!binding) throw new Error('binding is not declared on the active step');
|
|
454
|
+
const evidence = String(options.evidence || '').trim();
|
|
455
|
+
if (!evidence) throw new Error('--evidence is required for every binding disposition');
|
|
456
|
+
if (binding.required && disposition === 'not-needed') throw new Error('required bindings cannot be marked not-needed');
|
|
457
|
+
if (!binding.required && disposition === 'unavailable') throw new Error('optional bindings require loaded or not-needed disposition');
|
|
458
|
+
if (disposition === 'loaded') {
|
|
459
|
+
const resolution = resolveBinding(binding, context.root);
|
|
460
|
+
if (resolution.availability !== 'available') {
|
|
461
|
+
const reasonCode = resolution.availability === 'unknown' ? 'binding-path-unknown' : 'binding-path-missing';
|
|
462
|
+
if (binding.required) {
|
|
463
|
+
binding.status = 'unavailable';
|
|
464
|
+
binding.availability = resolution.availability;
|
|
465
|
+
binding.resolvedPath = null;
|
|
466
|
+
binding.evidence = evidence;
|
|
467
|
+
binding.reasonCode = reasonCode;
|
|
468
|
+
binding.updatedAt = new Date().toISOString();
|
|
469
|
+
atomicWriteJson(obligationPath(context.sessionDir), loaded.set);
|
|
470
|
+
}
|
|
471
|
+
throw new Error(resolution.availability === 'unknown'
|
|
472
|
+
? `${binding.required ? 'required' : 'optional'} binding ${binding.id} has no registered load path`
|
|
473
|
+
: `${binding.required ? 'required' : 'optional'} binding ${binding.id} path ${binding.path || binding.id} does not resolve to a readable file`);
|
|
474
|
+
}
|
|
475
|
+
binding.availability = 'available';
|
|
476
|
+
binding.resolvedPath = resolution.file;
|
|
477
|
+
}
|
|
478
|
+
binding.status = disposition;
|
|
479
|
+
binding.evidence = evidence;
|
|
480
|
+
binding.reasonCode = disposition === 'unavailable' ? String(options.reasonCode || 'binding-unavailable') : null;
|
|
481
|
+
binding.updatedAt = new Date().toISOString();
|
|
482
|
+
atomicWriteJson(obligationPath(context.sessionDir), loaded.set);
|
|
483
|
+
return { binding, activeStep: activeStepSnapshot(loaded.set) };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function syncTypedAggregateObligations(set) {
|
|
487
|
+
if (set.stepBindingsEnabled !== true) return;
|
|
488
|
+
const steps = set.requirements.filter(item => item?.stepType);
|
|
489
|
+
const groups = [
|
|
490
|
+
{
|
|
491
|
+
id: 'execute',
|
|
492
|
+
steps: steps.filter(step => step.stepType !== 'verification'),
|
|
493
|
+
evidencePrefix: 'typed-execution-steps',
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
id: 'verify',
|
|
497
|
+
steps: steps.filter(step => step.stepType === 'verification'),
|
|
498
|
+
evidencePrefix: 'typed-verification-steps',
|
|
499
|
+
},
|
|
500
|
+
];
|
|
501
|
+
const now = new Date().toISOString();
|
|
502
|
+
for (const group of groups) {
|
|
503
|
+
const noExecutionStep = group.id === 'execute' && group.steps.length === 0;
|
|
504
|
+
if (!noExecutionStep && (!group.steps.length || !group.steps.every(step => step.status === 'pass' && step.evidence))) continue;
|
|
505
|
+
const obligation = set.obligations.find(item => item.id === group.id);
|
|
506
|
+
if (!obligation) continue;
|
|
507
|
+
obligation.status = 'pass';
|
|
508
|
+
obligation.evidence = noExecutionStep
|
|
509
|
+
? 'typed-execution-steps:none (verification-only workflow)'
|
|
510
|
+
: `${group.evidencePrefix}:${group.steps.map(step => step.id).join(',')}`;
|
|
511
|
+
obligation.reasonCode = null;
|
|
512
|
+
obligation.scope = null;
|
|
513
|
+
obligation.updatedAt = now;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function updateStep(context, stepId, disposition, options = {}) {
|
|
518
|
+
if (!SAFE_ID.test(stepId || '')) throw new Error('--step-id must be a stable requirement id');
|
|
519
|
+
if (!['pass', 'blocked'].includes(disposition)) throw new Error('--disposition must be pass or blocked');
|
|
520
|
+
const plan = context.workflow?.pendingPlan || context.workflow?.workflowPlan;
|
|
521
|
+
const loaded = loadObligationSet(context, plan);
|
|
522
|
+
if (!loaded.set) throw new Error('workflow obligation contract unavailable or invalid: ' + loaded.error);
|
|
523
|
+
if (!loaded.set.stepBindingsEnabled) throw new Error('workflow has no active-step contract');
|
|
524
|
+
const step = activeStepRecord(loaded.set);
|
|
525
|
+
if (!step || step.id !== stepId) throw new Error('only the active step may receive a disposition');
|
|
526
|
+
const evidence = String(options.evidence || '').trim();
|
|
527
|
+
if (!evidence) throw new Error('--evidence is required for every step disposition');
|
|
528
|
+
const reasonCode = String(options.reasonCode || '').trim() || null;
|
|
529
|
+
if (disposition === 'blocked' && !reasonCode) throw new Error('--reason-code is required when blocking a step');
|
|
530
|
+
if (disposition === 'pass') {
|
|
531
|
+
const unresolvedRequired = step.requiredBindings.filter(binding => binding.status !== 'loaded');
|
|
532
|
+
const unresolvedOptional = step.optionalBindings.filter(binding => !['loaded', 'not-needed'].includes(binding.status));
|
|
533
|
+
if (unresolvedRequired.length || unresolvedOptional.length) {
|
|
534
|
+
const ids = [...unresolvedRequired, ...unresolvedOptional].map(binding => binding.id);
|
|
535
|
+
throw new Error('resolve active-step bindings before passing the step: ' + ids.join(', '));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
step.status = disposition;
|
|
539
|
+
step.evidence = evidence;
|
|
540
|
+
step.reasonCode = reasonCode;
|
|
541
|
+
step.updatedAt = new Date().toISOString();
|
|
542
|
+
syncTypedAggregateObligations(loaded.set);
|
|
543
|
+
loaded.set.activeStepId = activeStepRecord(loaded.set)?.id || null;
|
|
544
|
+
atomicWriteJson(obligationPath(context.sessionDir), loaded.set);
|
|
545
|
+
return { step, activeStep: activeStepSnapshot(loaded.set) };
|
|
546
|
+
}
|
|
547
|
+
|
|
281
548
|
function unresolvedObligations(context) {
|
|
282
549
|
const plan = context.workflow?.pendingPlan || context.workflow?.workflowPlan;
|
|
283
550
|
if (!needsPlanningContract(plan)) return [];
|
|
@@ -297,6 +564,21 @@ function unresolvedObligations(context) {
|
|
|
297
564
|
}
|
|
298
565
|
}
|
|
299
566
|
}
|
|
567
|
+
if (loaded.set.stepBindingsEnabled === true) {
|
|
568
|
+
const activeStep = activeStepRecord(loaded.set);
|
|
569
|
+
if (activeStep) {
|
|
570
|
+
if (activeStep.status === 'blocked') unresolved.push('step:' + activeStep.id + ':blocked');
|
|
571
|
+
else unresolved.push('step:' + activeStep.id + ':pending');
|
|
572
|
+
for (const binding of activeStep.requiredBindings) {
|
|
573
|
+
if (binding.status !== 'loaded') unresolved.push('binding:' + activeStep.id + ':' + binding.id + ':' + binding.status);
|
|
574
|
+
}
|
|
575
|
+
for (const binding of activeStep.optionalBindings) {
|
|
576
|
+
if (!['loaded', 'not-needed'].includes(binding.status)) {
|
|
577
|
+
unresolved.push('optional-binding:' + activeStep.id + ':' + binding.id + ':' + binding.status);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
300
582
|
return [...new Set(unresolved)];
|
|
301
583
|
}
|
|
302
584
|
|
|
@@ -312,5 +594,8 @@ module.exports = {
|
|
|
312
594
|
loadObligationSet,
|
|
313
595
|
planningUnresolved,
|
|
314
596
|
updateObligation,
|
|
597
|
+
updateBinding,
|
|
598
|
+
updateStep,
|
|
599
|
+
activeStepSnapshot,
|
|
315
600
|
unresolvedObligations,
|
|
316
601
|
};
|
|
@@ -7,7 +7,7 @@ const { atomicWriteJson, readJson } = require('./fable-contracts');
|
|
|
7
7
|
|
|
8
8
|
const OPEN_STATES = new Set(['pending', 'active', 'running', 'failed', 'blocked', 'escaped']);
|
|
9
9
|
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
10
|
-
const WORKFLOW_CONTROLLER_COMMANDS = new Set(['plan', 'start', 'revision', 'obligation', 'escape', 'block']);
|
|
10
|
+
const WORKFLOW_CONTROLLER_COMMANDS = new Set(['plan', 'start', 'revision', 'obligation', 'binding', 'stage-binding', 'step', 'escape', 'block']);
|
|
11
11
|
|
|
12
12
|
function activeWorkflowPlan(workflow) {
|
|
13
13
|
if (!workflow) return null;
|
|
@@ -99,6 +99,13 @@ function unresolvedStages(match, workflow, lastEditAt = 0) {
|
|
|
99
99
|
const escape = (workflow.escapes || []).find(item => item.stageId === stageId && item.runId === match.run.runId);
|
|
100
100
|
if (escape && escape.evidence && escape.uncoveredScope &&
|
|
101
101
|
['workflow-uncovered-scope', 'host-capability-unavailable'].includes(escape.reasonCode)) continue;
|
|
102
|
+
const bindings = [...(contract.requiredBindings || []), ...(contract.optionalBindings || [])];
|
|
103
|
+
const unresolvedBindings = bindings.filter(binding => binding.required !== false
|
|
104
|
+
? binding.status !== 'loaded'
|
|
105
|
+
: !['loaded', 'not-needed'].includes(binding.status));
|
|
106
|
+
if (unresolvedBindings.length) {
|
|
107
|
+
unresolved.push(`${stageId}:binding-unresolved:${unresolvedBindings.map(binding => binding.id).join(',')}`);
|
|
108
|
+
}
|
|
102
109
|
const evidence = readJson(path.join(match.runRoot, 'evidence', `${stageId}.json`));
|
|
103
110
|
if (contract.status !== 'pass' || !contract.checkCommand || !evidence || evidence.status !== 'pass' ||
|
|
104
111
|
evidence.exitCode !== 0 || evidence.planId !== contract.planId || evidence.runId !== contract.runId ||
|
|
@@ -6,11 +6,15 @@ const path = require('path');
|
|
|
6
6
|
const { loadWorkflow, saveWorkflow, matchingRun, OPEN_STATES, WORKFLOW_CONTROLLER_COMMANDS } = require('./lib/workflow-runtime');
|
|
7
7
|
const { getWorkspaceRoot, readCurrentSession } = require('./lib/harness-state');
|
|
8
8
|
const { atomicWriteJson, readJson } = require('./lib/fable-contracts');
|
|
9
|
+
const { resolveBinding } = require('./lib/binding-resolver');
|
|
9
10
|
const {
|
|
10
11
|
planningUnresolved,
|
|
11
12
|
recordPlanning,
|
|
12
13
|
materializeExecutionObligations,
|
|
13
14
|
updateObligation,
|
|
15
|
+
updateBinding,
|
|
16
|
+
updateStep,
|
|
17
|
+
activeStepSnapshot,
|
|
14
18
|
} = require('./lib/workflow-obligations');
|
|
15
19
|
|
|
16
20
|
const ALLOWED_ESCAPE_REASONS = new Set(['workflow-uncovered-scope', 'host-capability-unavailable']);
|
|
@@ -21,6 +25,7 @@ function parseArgs(argv) {
|
|
|
21
25
|
['--reason-code', 'reasonCode'], ['--scope', 'scope'], ['--evidence', 'evidence'],
|
|
22
26
|
['--session-id', 'sessionId'], ['--stage-id', 'stageId'],
|
|
23
27
|
['--obligation-id', 'obligationId'], ['--disposition', 'disposition'],
|
|
28
|
+
['--step-id', 'stepId'], ['--binding-id', 'bindingId'],
|
|
24
29
|
['--requirements-json', 'requirementsJson'], ['--strategy', 'strategy'],
|
|
25
30
|
]);
|
|
26
31
|
for (let i = 1; i < argv.length; i++) {
|
|
@@ -32,7 +37,7 @@ function parseArgs(argv) {
|
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
function usage() {
|
|
35
|
-
return `Usage: workflow-disposition.js <${[...WORKFLOW_CONTROLLER_COMMANDS].join('|')}> --session-id <id> [--requirements-json <json> --strategy <strategy> --obligation-id <id> --disposition <pass|escaped|
|
|
40
|
+
return `Usage: workflow-disposition.js <${[...WORKFLOW_CONTROLLER_COMMANDS].join('|')}> --session-id <id> [--requirements-json <json> --strategy <strategy> --obligation-id <id> --step-id <id> --binding-id <id> --disposition <pass|blocked|escaped|loaded|not-needed|unavailable> --reason-code <reason> --scope <scope> --evidence <evidence>]`;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
const SELECTABLE_STRATEGIES = new Set(['direct-single', 'iterative-single', 'fable-staged', 'fable-parallel', 'fable-multi-agent-workspace']);
|
|
@@ -65,6 +70,7 @@ function recomposePlan(workflow, requestedStrategy) {
|
|
|
65
70
|
taskShape,
|
|
66
71
|
actionGateReasonCodes: workflow.workflowPlan.actionGate?.reasonCodes || [],
|
|
67
72
|
reasonCodes: ['post-decomposition-workflow-selection'],
|
|
73
|
+
knowledgeSignals: workflow.workflowPlan.knowledgeSignals || [],
|
|
68
74
|
});
|
|
69
75
|
}
|
|
70
76
|
|
|
@@ -76,6 +82,61 @@ function consumer() {
|
|
|
76
82
|
throw new Error('Fable consumer unavailable');
|
|
77
83
|
}
|
|
78
84
|
|
|
85
|
+
function stageBindingDisposition(context, args) {
|
|
86
|
+
if (!String(context.workflow.strategy || '').startsWith('fable-')) throw new Error('stage bindings are only available on Fable workflows');
|
|
87
|
+
if (context.workflow.state !== 'running') throw new Error('start the selected Fable workflow before resolving stage bindings');
|
|
88
|
+
const match = matchingRun(context);
|
|
89
|
+
if (!match || !match.run.stageIds.includes(args.stageId)) throw new Error('--stage-id must name a stage in the correlated active run');
|
|
90
|
+
if (!['loaded', 'not-needed', 'unavailable'].includes(args.disposition)) throw new Error('--disposition must be loaded, not-needed, or unavailable');
|
|
91
|
+
const evidence = String(args.evidence || '').trim();
|
|
92
|
+
if (!evidence) throw new Error('--evidence is required for every binding disposition');
|
|
93
|
+
const active = consumer().activeStageBindings(match.runRoot, context.workflow.escapes);
|
|
94
|
+
if (!active.some(stage => stage.stageId === args.stageId)) throw new Error('stage bindings may only be resolved for a dependency-ready active stage');
|
|
95
|
+
const contractPath = path.join(match.runRoot, 'contracts', `${args.stageId}.json`);
|
|
96
|
+
const contract = readJson(contractPath);
|
|
97
|
+
if (!contract || contract.runId !== match.run.runId || contract.stageId !== args.stageId) throw new Error('stage contract is missing or uncorrelated');
|
|
98
|
+
const binding = [...(contract.requiredBindings || []), ...(contract.optionalBindings || [])]
|
|
99
|
+
.find(item => item.id === args.bindingId);
|
|
100
|
+
if (!binding) throw new Error('--binding-id must name a binding declared on the stage');
|
|
101
|
+
const required = binding.required !== false;
|
|
102
|
+
if (required && args.disposition === 'not-needed') throw new Error('required bindings cannot be marked not-needed');
|
|
103
|
+
if (!required && args.disposition === 'unavailable') throw new Error('optional bindings require loaded or not-needed disposition');
|
|
104
|
+
|
|
105
|
+
let disposition = args.disposition;
|
|
106
|
+
let reasonCode = disposition === 'unavailable' ? String(args.reasonCode || 'binding-unavailable') : null;
|
|
107
|
+
let availability = disposition === 'unavailable' ? 'unavailable' : binding.availability;
|
|
108
|
+
if (disposition === 'loaded') {
|
|
109
|
+
const resolution = resolveBinding(binding, context.root);
|
|
110
|
+
availability = resolution.availability;
|
|
111
|
+
binding.resolvedPath = resolution.file;
|
|
112
|
+
if (resolution.availability !== 'available') {
|
|
113
|
+
disposition = 'unavailable';
|
|
114
|
+
reasonCode = resolution.availability === 'unknown' ? 'binding-path-unknown' : 'binding-path-missing';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
binding.status = disposition;
|
|
118
|
+
binding.availability = availability;
|
|
119
|
+
binding.evidence = evidence;
|
|
120
|
+
binding.reasonCode = reasonCode;
|
|
121
|
+
binding.updatedAt = new Date().toISOString();
|
|
122
|
+
contract.updatedAt = binding.updatedAt;
|
|
123
|
+
atomicWriteJson(contractPath, contract);
|
|
124
|
+
return {
|
|
125
|
+
stageId: contract.stageId,
|
|
126
|
+
binding: { id: binding.id, required, status: binding.status, availability: binding.availability, path: binding.path, resolvedPath: binding.resolvedPath || null, reasonCode: binding.reasonCode },
|
|
127
|
+
activeStages: consumer().activeStageBindings(match.runRoot, context.workflow.escapes),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function activeStepCommandHints(sessionId, activeStep) {
|
|
132
|
+
if (!activeStep) return null;
|
|
133
|
+
const controller = path.resolve(__filename);
|
|
134
|
+
return {
|
|
135
|
+
resolveBinding: `node "${controller}" binding --session-id "${sessionId}" --step-id "${activeStep.id}" --binding-id "<active-binding-id>" --disposition <loaded|not-needed|unavailable> --evidence "<binding evidence>"`,
|
|
136
|
+
passStep: `node "${controller}" step --session-id "${sessionId}" --step-id "${activeStep.id}" --disposition pass --evidence "<completion evidence>"`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
79
140
|
function activatePlan(workflow, plan) {
|
|
80
141
|
workflow.workflowPlan = plan;
|
|
81
142
|
workflow.strategy = plan.strategy;
|
|
@@ -129,11 +190,14 @@ function main() {
|
|
|
129
190
|
delete workflow.blockReason;
|
|
130
191
|
const obligations = materializeExecutionObligations(context, plan);
|
|
131
192
|
saveWorkflow(context);
|
|
193
|
+
const activeStep = activeStepSnapshot(obligations);
|
|
132
194
|
process.stdout.write(JSON.stringify({
|
|
133
195
|
state: workflow.state,
|
|
134
196
|
workflowId: workflow.workflowId,
|
|
135
197
|
revision: workflow.revision,
|
|
136
198
|
obligations: obligations ? obligations.obligations.map(item => ({ id: item.id, status: item.status })) : [],
|
|
199
|
+
activeStep,
|
|
200
|
+
activeStepCommands: activeStepCommandHints(sessionId, activeStep),
|
|
137
201
|
}) + '\n');
|
|
138
202
|
return;
|
|
139
203
|
}
|
|
@@ -156,6 +220,11 @@ function main() {
|
|
|
156
220
|
workflow.escapes = [];
|
|
157
221
|
delete workflow.pendingPlan;
|
|
158
222
|
delete workflow.blockReason;
|
|
223
|
+
} else if (args.command === 'stage-binding') {
|
|
224
|
+
const result = stageBindingDisposition(context, args);
|
|
225
|
+
saveWorkflow(context);
|
|
226
|
+
process.stdout.write(JSON.stringify({ state: workflow.state, workflowId: workflow.workflowId, runId: workflow.runId, ...result }) + '\n');
|
|
227
|
+
return;
|
|
159
228
|
} else if (args.command === 'obligation') {
|
|
160
229
|
if (String(workflow.strategy || '').startsWith('fable-')) throw new Error('Fable workflows use correlated stage contracts, not generic execution obligations');
|
|
161
230
|
if (workflow.state !== 'running') throw new Error('start the selected workflow before recording execution obligation dispositions');
|
|
@@ -175,6 +244,46 @@ function main() {
|
|
|
175
244
|
},
|
|
176
245
|
}) + '\n');
|
|
177
246
|
return;
|
|
247
|
+
} else if (args.command === 'binding') {
|
|
248
|
+
if (String(workflow.strategy || '').startsWith('fable-')) throw new Error('Fable workflows use correlated stage contracts, not generic step bindings');
|
|
249
|
+
if (workflow.state !== 'running') throw new Error('start the selected workflow before resolving step bindings');
|
|
250
|
+
const result = updateBinding(context, args.stepId, args.bindingId, args.disposition, {
|
|
251
|
+
evidence: args.evidence,
|
|
252
|
+
reasonCode: args.reasonCode,
|
|
253
|
+
});
|
|
254
|
+
process.stdout.write(JSON.stringify({
|
|
255
|
+
state: workflow.state,
|
|
256
|
+
workflowId: workflow.workflowId,
|
|
257
|
+
binding: {
|
|
258
|
+
id: result.binding.id,
|
|
259
|
+
status: result.binding.status,
|
|
260
|
+
availability: result.binding.availability,
|
|
261
|
+
resolvedPath: result.binding.resolvedPath || null,
|
|
262
|
+
evidence: result.binding.evidence,
|
|
263
|
+
},
|
|
264
|
+
activeStep: result.activeStep,
|
|
265
|
+
activeStepCommands: activeStepCommandHints(sessionId, result.activeStep),
|
|
266
|
+
}) + '\n');
|
|
267
|
+
return;
|
|
268
|
+
} else if (args.command === 'step') {
|
|
269
|
+
if (String(workflow.strategy || '').startsWith('fable-')) throw new Error('Fable workflows use correlated stage contracts, not generic step dispositions');
|
|
270
|
+
if (workflow.state !== 'running') throw new Error('start the selected workflow before recording step dispositions');
|
|
271
|
+
const result = updateStep(context, args.stepId, args.disposition, {
|
|
272
|
+
evidence: args.evidence,
|
|
273
|
+
reasonCode: args.reasonCode,
|
|
274
|
+
});
|
|
275
|
+
process.stdout.write(JSON.stringify({
|
|
276
|
+
state: workflow.state,
|
|
277
|
+
workflowId: workflow.workflowId,
|
|
278
|
+
step: {
|
|
279
|
+
id: result.step.id,
|
|
280
|
+
status: result.step.status,
|
|
281
|
+
evidence: result.step.evidence,
|
|
282
|
+
},
|
|
283
|
+
activeStep: result.activeStep,
|
|
284
|
+
activeStepCommands: activeStepCommandHints(sessionId, result.activeStep),
|
|
285
|
+
}) + '\n');
|
|
286
|
+
return;
|
|
178
287
|
} else if (args.command === 'revision') {
|
|
179
288
|
console.error('[Workflow Reminder] Revision requested; no hard revision budget is enforced.');
|
|
180
289
|
} else if (args.command === 'escape') {
|
|
@@ -198,7 +307,12 @@ function main() {
|
|
|
198
307
|
} else throw new Error(usage());
|
|
199
308
|
|
|
200
309
|
saveWorkflow(context);
|
|
201
|
-
|
|
310
|
+
const match = matchingRun(context);
|
|
311
|
+
process.stdout.write(JSON.stringify({
|
|
312
|
+
state: workflow.state, workflowId: workflow.workflowId, runId: workflow.runId,
|
|
313
|
+
revision: workflow.revision, escapes: workflow.escapes,
|
|
314
|
+
activeStages: match && String(workflow.strategy || '').startsWith('fable-') ? consumer().activeStageBindings(match.runRoot, workflow.escapes) : undefined,
|
|
315
|
+
}) + '\n');
|
|
202
316
|
}
|
|
203
317
|
|
|
204
318
|
try { main(); } catch (error) { console.error('[Workflow Disposition] ' + error.message); process.exitCode = 2; }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "harness-everything",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.31.0",
|
|
5
5
|
"description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Miya Daniel",
|
|
@@ -12,7 +12,7 @@ const advisoryInstructions = [
|
|
|
12
12
|
`## 🚦 ENTRY TRIAGE & SEMANTIC CONTRACT`,
|
|
13
13
|
`For software/project work, load the \`harness-everything\` skill and establish routing context before broad mutation:`,
|
|
14
14
|
`1. MUST route before broad mutation; run \`npx github:dyphn1/Harness-everything next "<Brief summary of user's prompt>"\` when terminal execution is available and no equivalent routing state was injected.`,
|
|
15
|
-
`2. MUST
|
|
15
|
+
`2. MUST decompose Tier 2/3 work into ordered requirement steps with required/optional knowledge bindings. Load only active-step bindings; required bindings and applicable skill core contracts MUST resolve before the step passes.`,
|
|
16
16
|
`3. Selected-topology required obligations are MUSTs. Implementation tactics MAY adapt; numeric iteration/revision/replan/worker values MAY guide planning but never hard-block.`,
|
|
17
17
|
`4. MUST verify before claiming completion. Tier-3/Fable broad mutation MUST resolve isolation: linked worktree or explicit degraded fallback.`,
|
|
18
18
|
`5. Use the routing checkpoint only as internal source state for the first Harness Status; do not render a competing progress block.`,
|
|
@@ -148,7 +148,7 @@ function buildCodexGlobalContent() {
|
|
|
148
148
|
`- MUST establish relevant environment/host facts before relying on environment-sensitive behavior.`,
|
|
149
149
|
`- For software/project work, MUST establish routing before broad mutation; use \`npx github:dyphn1/Harness-everything next "<summary>"\` when terminal execution is available and routing state was not already injected.`,
|
|
150
150
|
`- For non-trivial work, MUST render one Markdown \`### 🚦 Harness Status\` with bullet-aligned bold \`Current\`, \`Read / Evidence\`, \`Next\`, and optional \`Risk / Blocked\`; repeat it at major phase/direction boundaries and before final completion.`,
|
|
151
|
-
`- MUST
|
|
151
|
+
`- MUST declare required/optional skills and references on ordered steps; load only active bindings and follow applicable skill core contracts before the step passes.`,
|
|
152
152
|
`- Selected-topology required obligations are MUSTs; implementation tactics MAY adapt and numeric planning values MAY guide but never hard-block.`,
|
|
153
153
|
`- Do not impose a universal TODO/TDD/Fable sequence; use the smallest useful topology plus applicable skills.`,
|
|
154
154
|
`- MUST verify before declaring done; a reminder-only host mechanism does not make verification optional.`,
|