harness-everything 0.30.10 → 0.31.1
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 +32 -4
- 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/claude-2026-10-13-issue-306/README.md +44 -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/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-2an-workflow-obligations.test.js +352 -3
- package/ci/mechanism-2aq-fable-stage-bindings.test.js +168 -0
- package/ci/mechanism-2ar-binding-resolution.test.js +211 -0
- package/ci/mechanism-35-verification-contract.test.js +186 -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 +7 -5
- package/docs/routing.md +16 -17
- package/docs/system-one-observations.md +1 -1
- 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/workflow-obligations.js +292 -7
- package/hooks/scripts/lib/workflow-runtime.js +8 -1
- package/hooks/scripts/state-persist.js +2 -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/workflow-obligations.js +292 -7
- package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +8 -1
- package/plugins/harness-everything/hooks/scripts/state-persist.js +2 -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/scripts/lib/verification-contract.js +78 -1
- 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/lib/advisory-text.js +2 -2
- package/scripts/lib/generated-skills.js +78 -0
- package/scripts/lib/verification-contract.js +78 -1
|
@@ -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.1",
|
|
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.`,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Metadata-only lookup of self-evolved skills registered in Harness
|
|
4
|
+
// manifests. The router uses it to emit step-input signals (ids only); the
|
|
5
|
+
// workflow runtime uses it to resolve a declared binding id to its SKILL.md
|
|
6
|
+
// once the declaring step is active. Nothing here reads or injects skill text.
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
11
|
+
const STOP_TRIGGERS = new Set(['the', 'and', 'for', 'with', 'your', 'this', 'that', 'some', 'from', 'prevent', 'resolved']);
|
|
12
|
+
|
|
13
|
+
function generatedManifestPaths(workspaceRoot, homeDir = process.env.HOME || process.env.USERPROFILE || '') {
|
|
14
|
+
const paths = [];
|
|
15
|
+
if (workspaceRoot) {
|
|
16
|
+
for (const platform of ['.claude', '.cursor', '.github', '.codex', '.continue']) {
|
|
17
|
+
paths.push(path.join(workspaceRoot, platform, 'harness-everything', 'manifest.json'));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (homeDir) {
|
|
21
|
+
paths.push(path.join(homeDir, '.agents', 'harness-everything', 'manifest.json'));
|
|
22
|
+
paths.push(path.join(homeDir, '.claude', 'harness-everything', 'manifest.json'));
|
|
23
|
+
}
|
|
24
|
+
return paths;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readGeneratedSkills(workspaceRoot, homeDir) {
|
|
28
|
+
const skills = new Map();
|
|
29
|
+
for (const manifestPath of generatedManifestPaths(workspaceRoot, homeDir)) {
|
|
30
|
+
let data;
|
|
31
|
+
try { data = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); } catch (_) { continue; }
|
|
32
|
+
if (!Array.isArray(data?.generated)) continue;
|
|
33
|
+
for (const entry of data.generated) {
|
|
34
|
+
const id = typeof entry?.id === 'string' ? entry.id.trim() : '';
|
|
35
|
+
if (!SAFE_ID.test(id) || skills.has(id) || typeof entry.dirPath !== 'string' || !entry.dirPath.trim()) continue;
|
|
36
|
+
const dirPath = path.isAbsolute(entry.dirPath)
|
|
37
|
+
? entry.dirPath
|
|
38
|
+
: path.resolve(workspaceRoot || path.dirname(manifestPath), entry.dirPath);
|
|
39
|
+
skills.set(id, {
|
|
40
|
+
id,
|
|
41
|
+
dirPath,
|
|
42
|
+
triggers: Array.isArray(entry.triggers) ? entry.triggers.filter(value => typeof value === 'string') : [],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return skills;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function triggerScore(promptLower, rawTrigger) {
|
|
50
|
+
const trigger = rawTrigger.toLowerCase().trim();
|
|
51
|
+
if (trigger.length < 2 || STOP_TRIGGERS.has(trigger)) return 0;
|
|
52
|
+
if (/[一-龥]/.test(trigger)) return promptLower.includes(trigger) ? trigger.length * 2 : 0;
|
|
53
|
+
const escaped = trigger.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
54
|
+
if (!new RegExp(`\\b${escaped}\\b`, 'i').test(promptLower)) return 0;
|
|
55
|
+
return trigger.length >= 4 ? 3 : 1.5;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Returns matching generated-skill ids, strongest match first.
|
|
59
|
+
function matchGeneratedSkills(promptLower, skills) {
|
|
60
|
+
const matched = [];
|
|
61
|
+
for (const skill of skills.values()) {
|
|
62
|
+
const score = skill.triggers.reduce((total, trigger) => total + triggerScore(promptLower, trigger), 0);
|
|
63
|
+
if (score >= 3) matched.push({ id: skill.id, score });
|
|
64
|
+
}
|
|
65
|
+
return matched.sort((a, b) => b.score - a.score).map(item => item.id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function findGeneratedSkill(id, workspaceRoot, homeDir) {
|
|
69
|
+
if (!SAFE_ID.test(id || '')) return null;
|
|
70
|
+
return readGeneratedSkills(workspaceRoot, homeDir).get(id) || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
generatedManifestPaths,
|
|
75
|
+
readGeneratedSkills,
|
|
76
|
+
matchGeneratedSkills,
|
|
77
|
+
findGeneratedSkill,
|
|
78
|
+
};
|
|
@@ -303,6 +303,7 @@ function verificationCommandSet(cwd, options = {}) {
|
|
|
303
303
|
contract,
|
|
304
304
|
contractInvalid: false,
|
|
305
305
|
commands: contract.checks.map(check => check.run),
|
|
306
|
+
checks: contract.checks.map(check => ({ run: check.run, cwd: check.cwd })),
|
|
306
307
|
commitHookDeclared: contract.checks.some(check => /^pre-commit\s+/i.test(normalizeCommand(check.run))),
|
|
307
308
|
};
|
|
308
309
|
}
|
|
@@ -323,7 +324,75 @@ function verificationCommandSet(cwd, options = {}) {
|
|
|
323
324
|
};
|
|
324
325
|
}
|
|
325
326
|
|
|
327
|
+
// Hosts such as Claude Code on Windows Bash may prefix a shell call with a
|
|
328
|
+
// working-directory prelude: `cd <workspace> && <command>`. Recognize exactly
|
|
329
|
+
// one such prelude lexically (never evaluating shell text) and only when the
|
|
330
|
+
// target is an existing directory inside the repository root. Only spellings
|
|
331
|
+
// whose Bash destination is stable are accepted: absolute paths or explicit
|
|
332
|
+
// ./ and ../ relatives. Bare relatives are CDPATH-sensitive, and option-like
|
|
333
|
+
// targets (-, --, -P ...) change cd semantics, so both fail closed. The remaining
|
|
334
|
+
// command must be one plain command: no further control operators,
|
|
335
|
+
// redirection, substitution, expansion, subshell, or background syntax.
|
|
336
|
+
const CWD_PRELUDE_RE = /^cd\s+(?:"([^"]*)"|'([^']*)'|([A-Za-z0-9_./:@%+,-]+))\s*&&\s*([\s\S]+)$/;
|
|
337
|
+
const UNSAFE_WRAPPED_RE = /[`$<>&|;()\r\n]/;
|
|
338
|
+
const UNSAFE_DOUBLE_QUOTED_RE = /[$`!]|\\[\\"$`]|\\$/;
|
|
339
|
+
|
|
340
|
+
function hostPathToNative(value, platform = process.platform) {
|
|
341
|
+
if (platform !== 'win32') return value;
|
|
342
|
+
const drive = value.match(/^\/([A-Za-z])(?:\/([\s\S]*))?$/);
|
|
343
|
+
return drive ? `${drive[1].toUpperCase()}:\\${(drive[2] || '').replace(/\//g, '\\')}` : value;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function stableCdTarget(target, platform = process.platform) {
|
|
347
|
+
if (target.startsWith('-')) return false;
|
|
348
|
+
if (/^\.\.?(?:\/|$)/.test(target)) return true;
|
|
349
|
+
if (platform === 'win32') {
|
|
350
|
+
if (/^\.\.?\\/.test(target) || /^[A-Za-z]:[\\/]/.test(target)) return true;
|
|
351
|
+
// Git Bash maps only /<drive>/... onto Windows paths; other absolute
|
|
352
|
+
// POSIX paths (/tmp, /usr) are MSYS mounts that Node cannot resolve alike.
|
|
353
|
+
return /^\/[A-Za-z](?:\/|$)/.test(target);
|
|
354
|
+
}
|
|
355
|
+
return target.startsWith('/');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function parseCwdPrelude(command, options = {}) {
|
|
359
|
+
const match = String(command || '').trim().match(CWD_PRELUDE_RE);
|
|
360
|
+
if (!match) return null;
|
|
361
|
+
const [, doubleQuoted, singleQuoted, bare, restRaw] = match;
|
|
362
|
+
const target = doubleQuoted ?? singleQuoted ?? bare;
|
|
363
|
+
const rest = restRaw.trim();
|
|
364
|
+
if (!target || !rest) return null;
|
|
365
|
+
if (doubleQuoted !== undefined && UNSAFE_DOUBLE_QUOTED_RE.test(target)) return null;
|
|
366
|
+
if (UNSAFE_WRAPPED_RE.test(rest) || hasControlOperators(rest)) return null;
|
|
367
|
+
if (!stableCdTarget(target, options.platform)) return null;
|
|
368
|
+
|
|
369
|
+
const cwd = path.resolve(options.cwd || process.cwd());
|
|
370
|
+
let resolved;
|
|
371
|
+
let root;
|
|
372
|
+
try {
|
|
373
|
+
resolved = fs.realpathSync.native(path.resolve(cwd, hostPathToNative(target, options.platform)));
|
|
374
|
+
if (!fs.statSync(resolved).isDirectory()) return null;
|
|
375
|
+
root = fs.realpathSync.native(path.resolve(options.root || findGitRoot(cwd)));
|
|
376
|
+
} catch (_) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
if (!within(root, resolved)) return null;
|
|
380
|
+
return { cwd: resolved, command: rest };
|
|
381
|
+
}
|
|
382
|
+
|
|
326
383
|
function isVerificationCommand(command, options = {}) {
|
|
384
|
+
const wrapped = parseCwdPrelude(command, options);
|
|
385
|
+
if (wrapped) return isPlainVerificationCommand(wrapped.command, { ...options, cwd: wrapped.cwd });
|
|
386
|
+
return isPlainVerificationCommand(command, options);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function cwdKey(dir) {
|
|
390
|
+
let resolved = path.resolve(dir);
|
|
391
|
+
try { resolved = fs.realpathSync.native(resolved); } catch (_) {}
|
|
392
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function isPlainVerificationCommand(command, options = {}) {
|
|
327
396
|
const cwd = path.resolve(options.cwd || process.cwd());
|
|
328
397
|
const actual = normalizeCommand(command);
|
|
329
398
|
if (!actual || hasControlOperators(actual)) return false;
|
|
@@ -333,8 +402,15 @@ function isVerificationCommand(command, options = {}) {
|
|
|
333
402
|
if (isGitCommit(actual)) {
|
|
334
403
|
return !!set.commitHookDeclared && !bypassesHooks(actual) && hasCommitHook(set.repoRoot);
|
|
335
404
|
}
|
|
405
|
+
// A declared contract check counts only when it ran in its declared cwd;
|
|
406
|
+
// otherwise a matching command in another directory (e.g. a subproject)
|
|
407
|
+
// would be credited as the root check.
|
|
408
|
+
if (set.contract) {
|
|
409
|
+
const executed = cwdKey(cwd);
|
|
410
|
+
return set.checks.some(check => commandMatches(actual, check.run) && cwdKey(check.cwd) === executed);
|
|
411
|
+
}
|
|
336
412
|
if (set.commands.some(expected => commandMatches(actual, expected))) return true;
|
|
337
|
-
return
|
|
413
|
+
return legacyVerificationCommand(actual);
|
|
338
414
|
}
|
|
339
415
|
|
|
340
416
|
module.exports = {
|
|
@@ -350,6 +426,7 @@ module.exports = {
|
|
|
350
426
|
isVerificationCommand,
|
|
351
427
|
legacyVerificationCommand,
|
|
352
428
|
normalizeCommand,
|
|
429
|
+
parseCwdPrelude,
|
|
353
430
|
readContract,
|
|
354
431
|
resolveContained,
|
|
355
432
|
verificationCommandSet,
|
|
@@ -37,6 +37,8 @@ Every planned stage declares dependency and write scope before dispatch:
|
|
|
37
37
|
"outputPath": "path/to/artifact-or-null",
|
|
38
38
|
"dependsOn": ["stage-1"],
|
|
39
39
|
"writeSet": ["src/auth", "tests/auth.test.js"],
|
|
40
|
+
"requiredBindings": [{ "id": "tdd", "path": "tdd/SKILL.md" }],
|
|
41
|
+
"optionalBindings": [{ "id": "security-review", "path": "security-review/SKILL.md" }],
|
|
40
42
|
"checkCommand": "node --test tests/auth.test.js",
|
|
41
43
|
"passCondition": "exit 0",
|
|
42
44
|
"failureReturn": "failure summary + evidence + missing prerequisite"
|
|
@@ -48,6 +50,7 @@ Rules:
|
|
|
48
50
|
- `stageId`, `goal`, `agent`, and `task` are required.
|
|
49
51
|
- `dependsOn` is always explicit. Use `[]` for a root stage.
|
|
50
52
|
- `writeSet` is always explicit. Use `[]` for a read-only stage.
|
|
53
|
+
- `requiredBindings` and `optionalBindings` are optional arrays. Each entry is a stable `id` and may declare a concrete repository-relative `path`; paths reject absolute names, globs, and traversal. Availability is `available` only when the path resolves to a readable file: workspace references resolve only under the workspace, while recognized Harness bindings (a registered id at its registered path, or a skill id naming its own `<id>/SKILL.md`) also search installed Harness skill roots (`missing` otherwise; `unverified` when no hook runtime is installed). An id without a path resolves through self-evolved manifest metadata, else remains visible with unknown availability. A binding that does not resolve cannot be recorded as `loaded`.
|
|
51
54
|
- `writeSet` contains concrete repository-relative path scopes, not globs,
|
|
52
55
|
absolute paths, or `..` traversal.
|
|
53
56
|
- a directory scope covers descendants; overlapping scopes cannot run in the
|
|
@@ -60,7 +63,7 @@ Rules:
|
|
|
60
63
|
|
|
61
64
|
## Workflow-plan consumer
|
|
62
65
|
|
|
63
|
-
For a session with an active mandatory workflow, write the stage array to the router-displayed `workflow-stages.json` path and invoke its `workflow-disposition.js start --session-id <id>` command. This binds the run to the active `workflowId` and bound workspace even after entering a linked worktree. Every required stage needs an objective check, and an independent plan needs a read-only `fable-verifier` stage depending on all other stages. Stage results need observed worker identities and numeric exit codes; missing metadata is
|
|
66
|
+
For a session with an active mandatory workflow, write the stage array to the router-displayed `workflow-stages.json` path and invoke its `workflow-disposition.js start --session-id <id>` command. This binds the run to the active `workflowId` and bound workspace even after entering a linked worktree. The start/check output exposes bindings only for dependency-ready stages; future-stage paths stay undisclosed. Resolve an active binding with `workflow-disposition.js stage-binding --stage-id <id> --binding-id <id> --disposition loaded|not-needed|unavailable --evidence <evidence>`. A successful check with unresolved bindings records `binding-unresolved`; resolve them, then rerun the exact check before the stage can pass. This state is a reminder, not a tool or Stop lock. Every required stage needs an objective check, and an independent plan needs a read-only `fable-verifier` stage depending on all other stages. Stage results need observed worker identities and numeric exit codes; missing metadata is unresolved, not success.
|
|
64
67
|
|
|
65
68
|
For standalone use without an active session contract, write the router contract and stage array to files and run:
|
|
66
69
|
|
|
@@ -4,7 +4,7 @@ description: "Stage large tasks through plans, named fable agents, failable chec
|
|
|
4
4
|
license: Apache-2.0
|
|
5
5
|
metadata:
|
|
6
6
|
author: Miya Daniel
|
|
7
|
-
version: 0.
|
|
7
|
+
version: 0.31.0
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Fable Mode
|
|
@@ -29,17 +29,15 @@ metadata:
|
|
|
29
29
|
|
|
30
30
|
## Select Operation First
|
|
31
31
|
|
|
32
|
-
For explanation/profile-only lookup, read only `<this-skill-dir>/references/profile-lookup.md` and answer. No stage state, delegation, or selector source reads. Quoted aliases do not start Fable. Mixed execution retains its topology.
|
|
33
|
-
|
|
34
|
-
Execution follows the workflow below.
|
|
32
|
+
For explanation/profile-only lookup, read only `<this-skill-dir>/references/profile-lookup.md` and answer. No stage state, delegation, or selector source reads. Quoted aliases do not start Fable. Mixed execution retains its topology; execution follows the workflow below.
|
|
35
33
|
|
|
36
34
|
## Workflow
|
|
37
35
|
|
|
38
36
|
1. Discover the runtime and lock the authorized file scope before edits.
|
|
39
|
-
2.
|
|
37
|
+
2. Per stage, set artifact, check, and required/optional bindings; dependency-ready stages disclose them, passing once resolved. Gaps never lock tools or Stop; advisory replans never halt. Rule-of-3 covers same-signature failures.
|
|
40
38
|
3. Resolve the requested behavior profile with `model-selector.js`; `opus`, `sonnet`/`sonnect`, and `haiku` are compatibility aliases, not model bindings.
|
|
41
|
-
4. Delegate by profile: orchestrator coordinates, reasoning
|
|
42
|
-
5.
|
|
43
|
-
6. Run
|
|
39
|
+
4. Delegate by profile: orchestrator coordinates, reasoning takes bounded judgment, mechanical takes low-ambiguity work; workers never spawn workers.
|
|
40
|
+
5. The host runtime-model floor is advisory; record it, never block on model choice alone.
|
|
41
|
+
6. Run stage checks, cold-review high-stakes artifacts, and audit profile/runtime/fallback fields.
|
|
44
42
|
|
|
45
43
|
Deep dive: <this-skill-dir>/references/model-matrix.md
|
|
@@ -45,6 +45,12 @@ first. Every stage declares `stageId`, goal, named agent, self-contained task,
|
|
|
45
45
|
expected output, `dependsOn`, and `writeSet` per
|
|
46
46
|
`fable-mode/CONTRACT-FORMAT.md`. `dependsOn: []` means no prerequisite;
|
|
47
47
|
`writeSet: []` means read-only. Workers never widen their own write set.
|
|
48
|
+
Declare stage-specific `requiredBindings` and `optionalBindings` in the same
|
|
49
|
+
contract. The consumer/start/check output exposes paths only for dependency-ready
|
|
50
|
+
stages; never preload bindings from future stages. Resolve active bindings with
|
|
51
|
+
`workflow-disposition.js stage-binding` and evidence before passing the stage.
|
|
52
|
+
If its objective check passes while bindings remain unresolved, the contract
|
|
53
|
+
records `binding-unresolved`; resolve the bindings and rerun that exact check.
|
|
48
54
|
|
|
49
55
|
Pass the router contract and stage array through
|
|
50
56
|
`fable-mode/scripts/workflow-plan-consumer.js` before spawning anything. Use the
|
|
@@ -97,10 +103,11 @@ that stage's check before continuing.
|
|
|
97
103
|
The run-scoped contract already exists before execution. When its exact
|
|
98
104
|
`checkCommand` runs, `contract-test.js` correlates it to one
|
|
99
105
|
`planId/runId/stageId`, updates that contract, and writes
|
|
100
|
-
`evidence/<stageId>.json`.
|
|
101
|
-
|
|
106
|
+
`evidence/<stageId>.json`. A `pass` requires both an objective check and
|
|
107
|
+
resolved declared bindings. If the command is ambiguous across runs, none of
|
|
108
|
+
the contracts is updated and the ambiguity must be resolved. Before delivery,
|
|
102
109
|
confirm every check-bearing contract in this run is `pass`; `planned`,
|
|
103
|
-
`running`, `pending`, or `
|
|
110
|
+
`running`, `pending`, `fail`, or `binding-unresolved` is not delivery evidence.
|
|
104
111
|
|
|
105
112
|
The subagent scope guard separately compares worker changes to the immutable
|
|
106
113
|
`writeSet` snapshot captured at burst start. An in-scope change is not proof of
|