harness-everything 0.8.0 → 0.9.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/.github/workflows/ci.yml +12 -2
- package/AGENTS.md +36 -35
- package/CHANGELOG.md +51 -0
- package/README.md +2 -2
- package/benchmarks/results/reviews/pr142-verification.json +95 -0
- package/ci/action-gate.test.js +83 -3
- package/ci/doc-capability-consistency.test.js +3 -0
- package/ci/invariant-routing.test.js +87 -74
- package/ci/mechanism-2af-worktree-isolation.test.js +152 -0
- package/ci/mechanism-2ag-router-worktree-contract.test.js +44 -0
- package/ci/mechanism-2ah-workflow-lifecycle.test.js +140 -0
- package/ci/openai-plugin-package.test.js +9 -6
- package/ci/phase4-ensemble-memory.test.js +128 -10
- package/ci/router-workflow-plan.test.js +23 -0
- package/ci/workflow-execution-contract.test.js +133 -0
- package/ci/workflow-plan-consumers.test.js +18 -0
- package/docs/architecture.md +111 -114
- package/docs/mechanism-first-skill-mesh.md +92 -103
- package/docs/openai-plugin.md +1 -1
- package/docs/philosophy.md +40 -33
- package/docs/platform-capabilities.md +2 -2
- package/docs/reviews/pr-142-issue-131.md +89 -0
- package/docs/routing.md +61 -124
- package/docs/workflow-runtime.md +109 -0
- package/fable-mode/CONTRACT-FORMAT.md +3 -1
- package/fable-mode/SKILL.md +1 -1
- package/fable-mode/agents/fable-orchestrator.md +9 -7
- 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 +13 -8
- package/harness-everything/SKILL.md +11 -10
- package/harness-everything/references/triage-and-tiers.md +76 -107
- package/harness-everything/schemas/router-task-shape.schema.json +4 -2
- package/harness-everything/schemas/router-workflow-plan.schema.json +3 -2
- package/harness-everything/scripts/kernel-router-core.js +265 -0
- package/harness-everything/scripts/kernel-router.js +118 -223
- package/harness-everything/scripts/router-contract-core.js +633 -0
- package/harness-everything/scripts/router-contract.js +21 -581
- package/harness-everything/scripts/tier-router.js +19 -1
- package/hooks/hooks.json +25 -4
- package/hooks/scripts/action-gate.js +111 -2
- package/hooks/scripts/contract-test.js +27 -2
- package/hooks/scripts/lib/workflow-isolation.js +102 -0
- package/hooks/scripts/lib/workflow-runtime.js +254 -0
- package/hooks/scripts/state-persist.js +1 -0
- package/hooks/scripts/subagent-scope-guard.js +20 -3
- package/hooks/scripts/workflow-disposition.js +104 -0
- package/hooks/scripts/workflow-gate.js +56 -0
- package/hooks/scripts/workflow-stop-gate.js +44 -0
- package/multi-agent-workspace/SKILL.md +2 -2
- package/multi-agent-workspace/scripts/index_memory.js +107 -1
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +2 -2
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/hooks.json +44 -1
- package/plugins/harness-everything/hooks/scripts/action-gate.js +111 -2
- package/plugins/harness-everything/hooks/scripts/contract-test.js +27 -2
- package/plugins/harness-everything/hooks/scripts/lib/workflow-isolation.js +102 -0
- package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +254 -0
- package/plugins/harness-everything/hooks/scripts/state-persist.js +1 -0
- package/plugins/harness-everything/hooks/scripts/subagent-scope-guard.js +20 -3
- package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +104 -0
- package/plugins/harness-everything/hooks/scripts/workflow-gate.js +56 -0
- package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +44 -0
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/scripts/lib/advisory-text.js +51 -54
- package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +3 -1
- package/plugins/harness-everything/skills/fable-mode/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +9 -7
- 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 +13 -8
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +11 -10
- package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +76 -107
- package/plugins/harness-everything/skills/harness-everything/schemas/router-task-shape.schema.json +4 -2
- package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +3 -2
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router-core.js +265 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +118 -223
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +633 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +21 -581
- package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +19 -1
- package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +2 -2
- package/plugins/harness-everything/skills/multi-agent-workspace/scripts/index_memory.js +107 -1
- package/plugins/harness-everything/skills/self-evolve/SKILL.md +6 -6
- package/plugins/harness-everything/skills/self-evolve/references/memory-resolution.md +22 -33
- package/plugins/harness-everything/skills/self-evolve/scripts/persist-memory.js +336 -57
- package/plugins/harness-everything/skills/self-evolve/scripts/self-regression.js +1 -0
- package/plugins/harness-everything/skills/using-git-worktrees/SKILL.md +14 -16
- package/plugins/harness-everything/skills/using-git-worktrees/references/workflow-details.md +44 -85
- package/scripts/lib/advisory-text.js +51 -54
- package/self-evolve/SKILL.md +6 -6
- package/self-evolve/references/memory-resolution.md +22 -33
- package/self-evolve/scripts/persist-memory.js +336 -57
- package/self-evolve/scripts/self-regression.js +1 -0
- package/using-git-worktrees/SKILL.md +14 -16
- package/using-git-worktrees/references/workflow-details.md +44 -85
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { loadWorkflow, saveWorkflow, matchingRun, OPEN_STATES, ensureWorkflowBudget, recordBudgetEvent, resetWorkflowBudget, syncBudgetToRun } = require('./lib/workflow-runtime');
|
|
7
|
+
const { getWorkspaceRoot, readCurrentSession } = require('./lib/harness-state');
|
|
8
|
+
const { atomicWriteJson, readJson } = require('./lib/fable-contracts');
|
|
9
|
+
|
|
10
|
+
const ALLOWED_ESCAPE_REASONS = new Set(['workflow-uncovered-scope', 'host-capability-unavailable']);
|
|
11
|
+
|
|
12
|
+
function parseArgs(argv) {
|
|
13
|
+
const args = { command: argv[0] };
|
|
14
|
+
const flags = new Map([['--reason-code', 'reasonCode'], ['--scope', 'scope'], ['--evidence', 'evidence'], ['--session-id', 'sessionId'], ['--stage-id', 'stageId']]);
|
|
15
|
+
for (let i = 1; i < argv.length; i++) {
|
|
16
|
+
const key = flags.get(argv[i]);
|
|
17
|
+
if (!key || !argv[i + 1] || argv[i + 1].startsWith('--')) throw new Error('unknown or incomplete argument: ' + argv[i]);
|
|
18
|
+
args[key] = argv[++i];
|
|
19
|
+
}
|
|
20
|
+
return args;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function consumer() {
|
|
24
|
+
for (const relative of ['../../fable-mode/scripts/workflow-plan-consumer.js', '../../skills/fable-mode/scripts/workflow-plan-consumer.js']) {
|
|
25
|
+
const file = path.resolve(__dirname, relative);
|
|
26
|
+
if (fs.existsSync(file)) return require(file);
|
|
27
|
+
}
|
|
28
|
+
throw new Error('Fable consumer unavailable');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function main() {
|
|
32
|
+
const args = parseArgs(process.argv.slice(2));
|
|
33
|
+
const sessionId = args.sessionId || readCurrentSession(getWorkspaceRoot());
|
|
34
|
+
if (!sessionId) throw new Error('no active Harness session; pass --session-id explicitly');
|
|
35
|
+
const context = loadWorkflow({ session_id: sessionId });
|
|
36
|
+
const { workflow } = context;
|
|
37
|
+
if (!workflow || !OPEN_STATES.has(workflow.state)) throw new Error('no unresolved workflow contract exists for this session');
|
|
38
|
+
if (args.command === 'start') {
|
|
39
|
+
if (!workflow.workflowId || !workflow.workflowPlan) throw new Error('legacy workflow lacks a correlated plan; cannot start it implicitly');
|
|
40
|
+
if (workflow.state === 'running') throw new Error('workflow already running; record a blocker before replanning');
|
|
41
|
+
const isReplan = workflow.revision > 0 || Boolean(workflow.runId);
|
|
42
|
+
if (isReplan) recordBudgetEvent(context, 'replan', { evidence: 'workflow-disposition:start' });
|
|
43
|
+
const plan = workflow.pendingPlan || workflow.workflowPlan;
|
|
44
|
+
if (!String(plan.strategy || '').startsWith('fable-')) {
|
|
45
|
+
workflow.revision++;
|
|
46
|
+
workflow.state = 'running';
|
|
47
|
+
delete workflow.blockReason;
|
|
48
|
+
ensureWorkflowBudget(workflow);
|
|
49
|
+
saveWorkflow(context);
|
|
50
|
+
process.stdout.write(JSON.stringify({ state: workflow.state, workflowId: workflow.workflowId, revision: workflow.revision }) + '\n');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const stages = readJson(path.join(context.sessionDir, 'workflow-stages.json'));
|
|
54
|
+
if (!Array.isArray(stages) || !stages.length) throw new Error('write a non-empty workflow-stages.json stage array at the session path first');
|
|
55
|
+
if (stages.some(stage => !stage.checkCommand || !stage.passCondition)) throw new Error('every required stage needs an objective checkCommand and passCondition');
|
|
56
|
+
if (plan.verification?.independent && !stages.some(stage => stage.agent === 'fable-verifier' && stage.writeSet?.length === 0 &&
|
|
57
|
+
stages.filter(other => other !== stage).every(other => stage.dependsOn?.includes(other.stageId)))) {
|
|
58
|
+
throw new Error('independent verification requires a read-only fable-verifier stage depending on all other stages');
|
|
59
|
+
}
|
|
60
|
+
const run = consumer().prepareRun({
|
|
61
|
+
routerContract: { workflowPlan: plan }, stages, workspaceRoot: context.root,
|
|
62
|
+
sessionId, workflowId: workflow.workflowId,
|
|
63
|
+
});
|
|
64
|
+
workflow.workflowPlan = plan;
|
|
65
|
+
workflow.strategy = plan.strategy;
|
|
66
|
+
workflow.tier = plan.tier;
|
|
67
|
+
workflow.verification = plan.verification;
|
|
68
|
+
workflow.mutationIsolation = plan.mutationIsolation || workflow.mutationIsolation;
|
|
69
|
+
workflow.runId = run.runId;
|
|
70
|
+
workflow.revision++;
|
|
71
|
+
workflow.state = 'running';
|
|
72
|
+
workflow.escapes = [];
|
|
73
|
+
delete workflow.pendingPlan;
|
|
74
|
+
delete workflow.blockReason;
|
|
75
|
+
ensureWorkflowBudget(workflow);
|
|
76
|
+
} else if (args.command === 'revision') {
|
|
77
|
+
recordBudgetEvent(context, 'revision', { evidence: args.evidence || 'workflow-disposition:revision' });
|
|
78
|
+
} else if (args.command === 'reset-budget') {
|
|
79
|
+
resetWorkflowBudget(context, args.evidence);
|
|
80
|
+
} else if (args.command === 'escape') {
|
|
81
|
+
if (!ALLOWED_ESCAPE_REASONS.has(args.reasonCode)) throw new Error('generic simple/routine/already-clear reasons are intentionally rejected');
|
|
82
|
+
if (!args.scope?.trim()) throw new Error('--scope is required');
|
|
83
|
+
if (!args.evidence?.trim()) throw new Error('--evidence is required');
|
|
84
|
+
const match = matchingRun(context);
|
|
85
|
+
if (!match || !match.run.stageIds.includes(args.stageId)) throw new Error('--stage-id must name a stage in the correlated active run');
|
|
86
|
+
const contract = readJson(path.join(match.runRoot, 'contracts', args.stageId + '.json'));
|
|
87
|
+
if (contract?.agent === 'fable-verifier') throw new Error('independent verification cannot be escaped; record BLOCKED when unavailable');
|
|
88
|
+
const disposition = {
|
|
89
|
+
status: 'escaped', stageId: args.stageId, runId: match.run.runId,
|
|
90
|
+
reasonCode: args.reasonCode, uncoveredScope: args.scope.trim(), evidence: args.evidence.trim(), recordedAt: new Date().toISOString(),
|
|
91
|
+
};
|
|
92
|
+
workflow.escapes = [...(workflow.escapes || []).filter(item => item.stageId !== args.stageId), disposition];
|
|
93
|
+
atomicWriteJson(path.join(match.runRoot, 'escapes', args.stageId + '.json'), disposition);
|
|
94
|
+
// Scope-limited exception; never change the entire workflow to escaped.
|
|
95
|
+
} else if (args.command === 'block') {
|
|
96
|
+
if (!args.evidence?.trim()) throw new Error('--evidence is required');
|
|
97
|
+
workflow.state = 'blocked';
|
|
98
|
+
workflow.blockReason = args.evidence.trim();
|
|
99
|
+
} else throw new Error('Usage: workflow-disposition.js <start|revision|reset-budget|escape|block> --session-id <id> [--stage-id <id> --reason-code <reason> --scope <scope> --evidence <evidence>]');
|
|
100
|
+
saveWorkflow(context);
|
|
101
|
+
syncBudgetToRun(context);
|
|
102
|
+
process.stdout.write(JSON.stringify({ state: workflow.state, workflowId: workflow.workflowId, runId: workflow.runId, revision: workflow.revision, escapes: workflow.escapes }) + '\n');
|
|
103
|
+
}
|
|
104
|
+
try { main(); } catch (error) { console.error('[Workflow Disposition] ' + error.message); process.exitCode = 2; }
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const { loadWorkflow, saveWorkflow, isMajorWorkflow, matchingRun, recordBudgetEvent, readHookInput } = require('./lib/workflow-runtime');
|
|
7
|
+
const { key, classifyShell, cwdOf, commandOf, mutationPaths, linkedWorktree, assertTargets, assertShellScope } = require('./lib/workflow-isolation');
|
|
8
|
+
|
|
9
|
+
const DIRECT = new Set(['Edit', 'Write', 'apply_patch']);
|
|
10
|
+
const SHELL = new Set(['Bash', 'PowerShell', 'exec_command']);
|
|
11
|
+
|
|
12
|
+
function isController(command, cwd) {
|
|
13
|
+
// One trusted local executable, literal arguments, no shell evaluation.
|
|
14
|
+
if (/[;&|`\r\n<>$(){}]/.test(command)) return false;
|
|
15
|
+
const match = command.match(/^node\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s+(start|escape|block)\b/);
|
|
16
|
+
return Boolean(match && key(path.resolve(cwd, match[1] || match[2] || match[3])) === key(path.join(__dirname, 'workflow-disposition.js')));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function decide(payload) {
|
|
20
|
+
if (!payload) return;
|
|
21
|
+
const tool = payload.tool_name || payload.tool;
|
|
22
|
+
if (!DIRECT.has(tool) && !SHELL.has(tool)) return;
|
|
23
|
+
const context = loadWorkflow(payload);
|
|
24
|
+
const { workflow, root, sessionDir } = context;
|
|
25
|
+
if (!workflow || workflow.state === 'deferred') return;
|
|
26
|
+
const cwd = cwdOf(payload, root);
|
|
27
|
+
const command = commandOf(payload);
|
|
28
|
+
const commandClass = SHELL.has(tool) ? classifyShell(command) : 'direct-mutation';
|
|
29
|
+
if (commandClass === 'read-only' || commandClass === 'worktree-setup') return;
|
|
30
|
+
if (SHELL.has(tool) && isController(command, cwd)) return;
|
|
31
|
+
// Only the stage specification is allowed before run entry. Other runtime
|
|
32
|
+
// state, including workflow-run.json, cannot use this bootstrap exception.
|
|
33
|
+
const targets = DIRECT.has(tool) ? mutationPaths(payload) : [];
|
|
34
|
+
const spec = path.join(sessionDir, 'workflow-stages.json');
|
|
35
|
+
const specStat = fs.existsSync(spec) ? fs.lstatSync(spec) : null;
|
|
36
|
+
if ((!specStat || (specStat.isFile() && !specStat.isSymbolicLink() && specStat.nlink === 1)) &&
|
|
37
|
+
targets.length && targets.every(target => key(path.resolve(cwd, target)) === key(spec))) return;
|
|
38
|
+
if (['blocked', 'failed'].includes(workflow.state)) throw new Error('workflow is BLOCKED; replan through the controller before mutation');
|
|
39
|
+
if (isMajorWorkflow(workflow)) {
|
|
40
|
+
const isolatedRoot = linkedWorktree(cwd, root);
|
|
41
|
+
if (!isolatedRoot) throw new Error('Git worktree isolation is required before mutation. Enter a linked worktree; unavailable isolation means BLOCKED, never in-place fallback.');
|
|
42
|
+
if (DIRECT.has(tool)) assertTargets(payload, cwd, isolatedRoot);
|
|
43
|
+
else assertShellScope(command, cwd, isolatedRoot);
|
|
44
|
+
}
|
|
45
|
+
if (workflow.state === 'satisfied') throw new Error('workflow is already satisfied; submit a new task before further mutation');
|
|
46
|
+
if (String(workflow.strategy || '').startsWith('fable-') && !matchingRun(context)) {
|
|
47
|
+
throw new Error('selected Fable workflow has no correlated run. Write workflow-stages.json at the displayed session path, then use workflow-disposition.js start.');
|
|
48
|
+
}
|
|
49
|
+
if (workflow.strategy === 'iterative-single') {
|
|
50
|
+
recordBudgetEvent(context, 'iteration', { evidence: `${tool}:${commandClass}` });
|
|
51
|
+
}
|
|
52
|
+
workflow.lastMutationAt = Date.now();
|
|
53
|
+
saveWorkflow(context);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
readHookInput(decide);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { readJson } = require('./lib/fable-contracts');
|
|
6
|
+
const { loadWorkflow, saveWorkflow, matchingRun, unresolvedStages, readHookInput } = require('./lib/workflow-runtime');
|
|
7
|
+
|
|
8
|
+
function decide(payload) {
|
|
9
|
+
if (!payload) return;
|
|
10
|
+
const context = loadWorkflow(payload);
|
|
11
|
+
const { workflow, sessionDir } = context;
|
|
12
|
+
if (!workflow || ['deferred', 'satisfied'].includes(workflow.state)) return;
|
|
13
|
+
if (workflow.state === 'blocked') {
|
|
14
|
+
console.error('[Workflow Completion Gate] BLOCKED: report the unresolved work; completion has not been verified.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let unresolved = [];
|
|
18
|
+
if (String(workflow.strategy || '').startsWith('fable-')) {
|
|
19
|
+
const match = matchingRun(context);
|
|
20
|
+
unresolved = match ? unresolvedStages(match, workflow) : ['correlated-run-missing'];
|
|
21
|
+
} else {
|
|
22
|
+
const handoff = readJson(path.join(sessionDir, 'handoff-state.json'));
|
|
23
|
+
const lastEdit = Math.max(workflow.lastMutationAt || 0, handoff?.lastEditAt || 0);
|
|
24
|
+
if (lastEdit && ((handoff?.lastVerifyAt || 0) < lastEdit || handoff?.lastVerifyExitCode !== 0)) unresolved = ['verification-after-edit-missing'];
|
|
25
|
+
}
|
|
26
|
+
if (unresolved.length) {
|
|
27
|
+
workflow.unresolved = unresolved;
|
|
28
|
+
if (payload.stop_hook_active) {
|
|
29
|
+
// Do not loop forever or pretend the retry satisfied the contract.
|
|
30
|
+
workflow.state = 'blocked';
|
|
31
|
+
workflow.blockReason = 'unresolved-stop-retry';
|
|
32
|
+
saveWorkflow(context);
|
|
33
|
+
console.error('[Workflow Completion Gate] BLOCKED: ' + unresolved.join(', ') + '. Report the blocker; this is not completion.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
saveWorkflow(context);
|
|
37
|
+
throw new Error('Cannot complete: unresolved workflow obligations: ' + unresolved.join(', '));
|
|
38
|
+
}
|
|
39
|
+
workflow.state = 'satisfied';
|
|
40
|
+
workflow.resolvedAt = new Date().toISOString();
|
|
41
|
+
workflow.unresolved = [];
|
|
42
|
+
saveWorkflow(context);
|
|
43
|
+
}
|
|
44
|
+
readHookInput(decide);
|
|
@@ -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.9.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",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// Prompt-injection
|
|
2
|
-
//
|
|
3
|
-
// truth for the advisory block.
|
|
1
|
+
// Prompt-injection guidance for integration paths where Harness cannot prove
|
|
2
|
+
// lifecycle-hook enforcement. Single source of truth for advisory blocks.
|
|
4
3
|
const fs = require('fs');
|
|
5
4
|
const path = require('path');
|
|
6
5
|
|
|
@@ -8,34 +7,36 @@ const MARKER = "Harness OS Guidance (Advisory)";
|
|
|
8
7
|
|
|
9
8
|
const advisoryInstructions = [
|
|
10
9
|
`\n# ${MARKER}`,
|
|
11
|
-
`This
|
|
12
|
-
`
|
|
13
|
-
`
|
|
10
|
+
`This surface cannot be assumed to mechanically enforce every Harness lifecycle transition.`,
|
|
11
|
+
`Treat the workflow contract below as required agent behavior, while keeping enforcement`,
|
|
12
|
+
`claims bounded by the host's verified capabilities.`,
|
|
14
13
|
``,
|
|
15
|
-
`## 🚦 MANDATORY ENTRY TRIAGE & ROUTING
|
|
16
|
-
`
|
|
17
|
-
`1. Run \`npx github:dyphn1/Harness-everything next "<Brief summary of user's prompt>"
|
|
18
|
-
`2. For EVERY suggested skill,
|
|
19
|
-
`3.
|
|
20
|
-
`4.
|
|
14
|
+
`## 🚦 MANDATORY ENTRY TRIAGE & WORKFLOW ROUTING`,
|
|
15
|
+
`For every software/project prompt, load the \`harness-everything\` skill and establish the routing contract before mutation:`,
|
|
16
|
+
`1. Run \`npx github:dyphn1/Harness-everything next "<Brief summary of user's prompt>"\`. If terminal execution is unavailable, apply the same contract from the skill instead of skipping routing.`,
|
|
17
|
+
`2. For EVERY suggested skill, read its complete \`SKILL.md\` entry and resolve applicability from \`USE FOR\`, \`DO NOT USE FOR\`, workflow/basic flow, and hard rules. A name, description, router summary, or "routine/simple/already clear" judgement is not enough to omit it.`,
|
|
18
|
+
`3. The selected workflow topology is mandatory once applicable. Execute it to resolution; choose tools, reasoning, implementation technique, and decomposition freely inside it. Do not replace it with a direct path because you believe you can solve the task without the workflow.`,
|
|
19
|
+
`4. If the selected workflow genuinely cannot cover part of the task, record an explicit escape for the uncovered scope with evidence. Covered obligations remain mandatory. Host-capability loss must be surfaced, not silently converted into optional execution.`,
|
|
20
|
+
`5. Surface a compact checkpoint near the first progress update:`,
|
|
21
21
|
` \`\`\`markdown`,
|
|
22
22
|
` ## 🚦 Harness OS Routing Checkpoint`,
|
|
23
|
-
` - **Active Tier**: Tier 1
|
|
24
|
-
` - **
|
|
23
|
+
` - **Active Tier**: Tier 1 | Tier 2 | Tier 3 | unclassified`,
|
|
24
|
+
` - **Strategy**: <selected/deferred strategy>`,
|
|
25
|
+
` - **Workflow State**: <active | deferred | blocked | escaped>`,
|
|
25
26
|
` - **Required Invariants**: <router invariants>`,
|
|
26
|
-
` - **Suggested Skills**: <deduplicated
|
|
27
|
-
` - **
|
|
28
|
-
` - **
|
|
27
|
+
` - **Suggested Skills**: <deduplicated suggestions or none>`,
|
|
28
|
+
` - **Applicability**: <use | not-applicable + reason | unresolved/unavailable>`,
|
|
29
|
+
` - **Escape**: <none | reason + uncovered scope + evidence>`,
|
|
29
30
|
` \`\`\``,
|
|
30
31
|
``,
|
|
31
|
-
`## 🤖 COGNITIVE COMPLIANCE
|
|
32
|
-
`- **
|
|
33
|
-
`- **
|
|
34
|
-
`- **Pre-
|
|
35
|
-
`- **
|
|
36
|
-
`- **Environment
|
|
37
|
-
`- **
|
|
38
|
-
`- **Prefer
|
|
32
|
+
`## 🤖 COGNITIVE COMPLIANCE`,
|
|
33
|
+
`- **No universal skill pipeline**: do not force every task through TODO/TDD/Fable. The router selects the smallest sufficient topology; suggested skills are resolved for applicability.`,
|
|
34
|
+
`- **Applicable skill flow**: reading a skill is not permission to discard it when its workflow actually applies.`,
|
|
35
|
+
`- **Pre-completion verification**: before declaring done, run \`npx github:dyphn1/Harness-everything verify\` in the project root and fix failures. An instruction-only host may not block you mechanically, but the requirement remains part of the contract.`,
|
|
36
|
+
`- **Rule of 3**: after 3 same-signature failures, stop micro-retrying and use a fresh diagnosis / zoom-out.`,
|
|
37
|
+
`- **Environment discovery**: discover OS, shell, package manager/runtime, and host capability before relying on them.`,
|
|
38
|
+
`- **Memory / self-evolve**: persist only verified reusable lessons; use escape/replan/recovery evidence to improve workflow coverage rather than bypassing the active workflow.`,
|
|
39
|
+
`- **Prefer editing**: prefer targeted edits and logically complete commits over broad rewrites.`,
|
|
39
40
|
``
|
|
40
41
|
].join('\n');
|
|
41
42
|
|
|
@@ -70,30 +71,27 @@ function injectAdvisoryText(targetFile, header, label) {
|
|
|
70
71
|
function removeAdvisoryText(targetFile) {
|
|
71
72
|
if (!fs.existsSync(targetFile)) return;
|
|
72
73
|
try {
|
|
73
|
-
|
|
74
|
+
const content = fs.readFileSync(targetFile, 'utf8');
|
|
74
75
|
const markerIndex = content.indexOf(MARKER);
|
|
75
|
-
if (markerIndex
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
if (markerIndex === -1) return;
|
|
77
|
+
let cleanContent = content;
|
|
78
|
+
const hashMarkerIndex = content.lastIndexOf('#', markerIndex);
|
|
79
|
+
if (hashMarkerIndex !== -1) cleanContent = content.substring(0, hashMarkerIndex).trim() + '\n';
|
|
80
|
+
else cleanContent = content.substring(0, markerIndex).trim() + '\n';
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
82
|
+
const lines = cleanContent.trim().split('\n').map(l => l.trim()).filter(Boolean);
|
|
83
|
+
if (lines.length === 0 || (lines.length === 1 && ['# Cursor Project Rules', '# AGENTS.md', '# Copilot Instructions', '# .hermes.md'].includes(lines[0]))) {
|
|
84
|
+
fs.unlinkSync(targetFile);
|
|
85
|
+
console.log(` ✅ Removed empty advisory file: ${targetFile}`);
|
|
86
|
+
} else {
|
|
87
|
+
fs.writeFileSync(targetFile, cleanContent.trim() + '\n', 'utf8');
|
|
88
|
+
console.log(` ✅ Removed Harness guidance from: ${targetFile}`);
|
|
89
89
|
}
|
|
90
90
|
} catch (e) {
|
|
91
91
|
console.warn(` ⚠️ Error removing advisory text from ${targetFile}: ${e.message}`);
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
// Continue gets a dedicated rule file. If a user already owns that exact
|
|
96
|
-
// path, do not replace it just because Harness chose the same filename.
|
|
97
95
|
function installContinueRule(targetFile, label) {
|
|
98
96
|
const dir = path.dirname(targetFile);
|
|
99
97
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
@@ -103,7 +101,7 @@ function installContinueRule(targetFile, label) {
|
|
|
103
101
|
`---`,
|
|
104
102
|
`name: Harness OS Guidance`,
|
|
105
103
|
`alwaysApply: true`,
|
|
106
|
-
`description: "Harness OS routing
|
|
104
|
+
`description: "Harness OS routing and workflow guidance (instruction-governed on Continue unless a lifecycle mechanism is independently verified)"`,
|
|
107
105
|
`---`,
|
|
108
106
|
advisoryInstructions.replace(/^\n/, '')
|
|
109
107
|
].join('\n');
|
|
@@ -139,25 +137,24 @@ function buildCopilotGlobalContent() {
|
|
|
139
137
|
function buildCodexGlobalContent() {
|
|
140
138
|
return [
|
|
141
139
|
`---`,
|
|
142
|
-
`description: "Harness OS Guidance - Global custom agent for orchestrating
|
|
140
|
+
`description: "Harness OS Guidance - Global custom agent for orchestrating software workflows"`,
|
|
143
141
|
`name: "Harness"`,
|
|
144
142
|
`user-invocable: true`,
|
|
145
143
|
`---`,
|
|
146
144
|
`# AGENTS.md`,
|
|
147
145
|
``,
|
|
148
146
|
`# ${MARKER}`,
|
|
149
|
-
`This
|
|
150
|
-
`enforce it mechanically. Treat these as strong defaults, not guarantees.`,
|
|
147
|
+
`This instruction surface cannot be assumed to provide hard lifecycle blocking.`,
|
|
151
148
|
``,
|
|
152
|
-
`- Discover the environment
|
|
149
|
+
`- Discover the environment and host capabilities before relying on them.`,
|
|
153
150
|
`- For software/project work, run \`npx github:dyphn1/Harness-everything next "<summary>"\` before mutation and surface the routing checkpoint.`,
|
|
154
|
-
`-
|
|
155
|
-
`-
|
|
156
|
-
`- Do not impose a universal TODO/TDD/Fable sequence
|
|
157
|
-
`-
|
|
158
|
-
|
|
159
|
-
`-
|
|
160
|
-
`- Prefer
|
|
151
|
+
`- Read every suggested skill's complete \`SKILL.md\` entry before resolving applicability; do not omit it from metadata or confidence alone.`,
|
|
152
|
+
`- Execute the selected workflow topology to resolution. The model controls HOW inside the topology, not WHETHER the selected topology exists.`,
|
|
153
|
+
`- Do not impose a universal TODO/TDD/Fable sequence; use the smallest selected topology plus applicable skills.`,
|
|
154
|
+
`- Escape only for genuinely uncovered workflow scope and record reason + scope + evidence.`,
|
|
155
|
+
`- Before declaring done, run \`npx github:dyphn1/Harness-everything verify\` and fix failures.`,
|
|
156
|
+
`- After 3 same-signature failures, stop retrying and use a fresh diagnosis / zoom-out.`,
|
|
157
|
+
`- Prefer targeted edits and logically complete commits.`,
|
|
161
158
|
``
|
|
162
159
|
].join('\n');
|
|
163
160
|
}
|
|
@@ -173,4 +170,4 @@ module.exports = {
|
|
|
173
170
|
removeContinueRule,
|
|
174
171
|
buildCopilotGlobalContent,
|
|
175
172
|
buildCodexGlobalContent,
|
|
176
|
-
};
|
|
173
|
+
};
|
|
@@ -59,7 +59,9 @@ Rules:
|
|
|
59
59
|
|
|
60
60
|
## Workflow-plan consumer
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
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 blocked/degraded, not success.
|
|
63
|
+
|
|
64
|
+
For standalone use without an active session contract, write the router contract and stage array to files and run:
|
|
63
65
|
|
|
64
66
|
```bash
|
|
65
67
|
node fable-mode/scripts/workflow-plan-consumer.js \
|
|
@@ -53,9 +53,10 @@ overlap reject dispatch instead of becoming a verbal warning.
|
|
|
53
53
|
|
|
54
54
|
The stage map remains a living orchestration document only inside those
|
|
55
55
|
constraints. When new information invalidates it, re-plan and create revised
|
|
56
|
-
stage contracts before dispatching new work. Replan budget
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
stage contracts before dispatching new work. Replan budget comes from `workflowPlan.limits.maxReplans` and is accounted by
|
|
57
|
+
the runtime controller. Re-enter through `workflow-disposition.js start`; a
|
|
58
|
+
third full replan under the default budget is machine-blocked as
|
|
59
|
+
`replan-budget-exhausted` instead of being only an orchestration reminder. Scope rule:
|
|
59
60
|
deliver the task as specified; new scope discovered mid-run is surfaced as a
|
|
60
61
|
recommendation at delivery, not silently built.
|
|
61
62
|
|
|
@@ -75,10 +76,11 @@ spawn workers.
|
|
|
75
76
|
|
|
76
77
|
For `fable-parallel`, spawn only stages in the same validated execution batch;
|
|
77
78
|
never invent an additional parallel edge. A stage depending on another stage is
|
|
78
|
-
not in the same ready batch.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
not in the same ready batch. Never exceed `workflowPlan.limits.maxWorkers`. The router plan is the numeric
|
|
80
|
+
source of truth; `workflow-plan-consumer.js` chunks ready sets to that cap and
|
|
81
|
+
runtime worker leases enforce it when the host exposes stable worker IDs. For
|
|
82
|
+
other Fable strategies, serialize the returned batches unless a later validated
|
|
83
|
+
plan says otherwise.
|
|
82
84
|
|
|
83
85
|
**3. Verify with a check that can fail — external artifacts only.** Each stage
|
|
84
86
|
defines a pass condition an external artifact satisfies: a test that runs, a file
|
|
@@ -155,20 +155,23 @@ function validateParallelBatch(stages) {
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
function dependencyBatches(stages, validateWrites) {
|
|
158
|
+
function dependencyBatches(stages, validateWrites, maxBatchSize = Infinity) {
|
|
159
159
|
const byId = new Map(stages.map(stage => [stage.stageId, stage]));
|
|
160
160
|
const remaining = new Set(byId.keys());
|
|
161
161
|
const completed = new Set();
|
|
162
162
|
const batches = [];
|
|
163
|
+
const boundedSize = Number.isInteger(maxBatchSize) && maxBatchSize > 0 ? maxBatchSize : Infinity;
|
|
163
164
|
|
|
164
165
|
while (remaining.size > 0) {
|
|
165
166
|
const ready = [...remaining]
|
|
166
167
|
.filter(stageId => byId.get(stageId).dependsOn.every(dep => completed.has(dep)))
|
|
167
168
|
.sort();
|
|
168
169
|
if (ready.length === 0) throw new Error('unresolved or cyclic dependency graph');
|
|
169
|
-
const
|
|
170
|
-
if (validateWrites) validateParallelBatch(
|
|
171
|
-
|
|
170
|
+
const readyStages = ready.map(stageId => byId.get(stageId));
|
|
171
|
+
if (validateWrites) validateParallelBatch(readyStages);
|
|
172
|
+
for (let offset = 0; offset < readyStages.length; offset += boundedSize) {
|
|
173
|
+
batches.push(readyStages.slice(offset, offset + boundedSize).map(stage => stage.stageId));
|
|
174
|
+
}
|
|
172
175
|
for (const stageId of ready) {
|
|
173
176
|
remaining.delete(stageId);
|
|
174
177
|
completed.add(stageId);
|
|
@@ -177,8 +180,8 @@ function dependencyBatches(stages, validateWrites) {
|
|
|
177
180
|
return batches;
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
function topologicalBatches(stages) {
|
|
181
|
-
return dependencyBatches(stages, true);
|
|
183
|
+
function topologicalBatches(stages, maxWorkers) {
|
|
184
|
+
return dependencyBatches(stages, true, maxWorkers);
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
function sequentialBatches(stages) {
|
|
@@ -209,7 +212,7 @@ function atomicWriteJson(filePath, value) {
|
|
|
209
212
|
fs.renameSync(tempPath, filePath);
|
|
210
213
|
}
|
|
211
214
|
|
|
212
|
-
function prepareRun({ routerContract, stages, workspaceRoot, runId, sessionId = null }) {
|
|
215
|
+
function prepareRun({ routerContract, stages, workspaceRoot, runId, sessionId = null, workflowId = null }) {
|
|
213
216
|
const plan = validateRouterPlan(routerContract);
|
|
214
217
|
const normalizedStages = validateStageGraph(stages || []);
|
|
215
218
|
if (normalizedStages.length === 0) throw new Error('at least one stage contract is required');
|
|
@@ -222,7 +225,7 @@ function prepareRun({ routerContract, stages, workspaceRoot, runId, sessionId =
|
|
|
222
225
|
if (fs.existsSync(runRoot)) throw new Error(`runId already exists in this workspace: ${resolvedRunId}`);
|
|
223
226
|
|
|
224
227
|
const batches = plan.strategy === 'fable-parallel'
|
|
225
|
-
? topologicalBatches(normalizedStages)
|
|
228
|
+
? topologicalBatches(normalizedStages, plan.limits && plan.limits.maxWorkers)
|
|
226
229
|
: sequentialBatches(normalizedStages);
|
|
227
230
|
|
|
228
231
|
const createdAt = new Date().toISOString();
|
|
@@ -237,6 +240,7 @@ function prepareRun({ routerContract, stages, workspaceRoot, runId, sessionId =
|
|
|
237
240
|
planId,
|
|
238
241
|
runId: resolvedRunId,
|
|
239
242
|
sessionId: sessionId || null,
|
|
243
|
+
workflowId,
|
|
240
244
|
workspaceRoot: root,
|
|
241
245
|
...stage,
|
|
242
246
|
workerId: null,
|
|
@@ -252,6 +256,7 @@ function prepareRun({ routerContract, stages, workspaceRoot, runId, sessionId =
|
|
|
252
256
|
planId,
|
|
253
257
|
runId: resolvedRunId,
|
|
254
258
|
sessionId: sessionId || null,
|
|
259
|
+
workflowId,
|
|
255
260
|
workspaceRoot: root,
|
|
256
261
|
workspaceKey: getWorkspaceKey(root),
|
|
257
262
|
strategy: plan.strategy,
|
|
@@ -4,7 +4,7 @@ description: "Route software/project work through the Harness kernel: classify T
|
|
|
4
4
|
license: Apache-2.0
|
|
5
5
|
metadata:
|
|
6
6
|
author: Miya Daniel
|
|
7
|
-
version: 0.
|
|
7
|
+
version: 0.9.0
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Harness Everything
|
|
@@ -12,26 +12,27 @@ metadata:
|
|
|
12
12
|
Kernel routing entry.
|
|
13
13
|
|
|
14
14
|
## USE FOR:
|
|
15
|
-
- Software/project triage, re-routing, and
|
|
15
|
+
- Software/project triage, routing, re-routing, and workflow selection.
|
|
16
16
|
- Work that names or matches another skill.
|
|
17
17
|
|
|
18
18
|
## DO NOT USE FOR:
|
|
19
19
|
- General Q&A or non-software writing.
|
|
20
20
|
|
|
21
21
|
## Invariants
|
|
22
|
-
1. **Route before execution** — establish scope/tier.
|
|
22
|
+
1. **Route before execution** — establish scope/tier and the smallest applicable workflow.
|
|
23
23
|
2. **Verify before claim** — require objective evidence.
|
|
24
24
|
3. **Re-plan on repetition** — after 3 same-signature failures, use `zoom-out`.
|
|
25
|
-
4. **Evaluate before
|
|
26
|
-
|
|
27
|
-
Tier guidance is not a fixed pipeline.
|
|
25
|
+
4. **Evaluate before omission** — read each suggested skill's `SKILL.md` before deciding applicability.
|
|
26
|
+
5. **Resolve selected workflow** — model confidence is not an escape condition.
|
|
28
27
|
|
|
29
28
|
## Workflow
|
|
30
29
|
1. Reuse kernel output, or run `node "<this-skill-dir>/scripts/kernel-router.js" "<prompt summary>"`.
|
|
31
|
-
2.
|
|
32
|
-
3.
|
|
33
|
-
4.
|
|
30
|
+
2. Surface the **Harness Routing Checkpoint**.
|
|
31
|
+
3. Read each suggested `SKILL.md`; evaluate `USE FOR`, `DO NOT USE FOR`, workflow/basic flow, and hard rules. "Simple", "routine", names, or summaries alone cannot justify omission.
|
|
32
|
+
4. Execute the selected workflow topology to resolution. Suggested skills are conditional on applicability; the selected topology is not advisory.
|
|
33
|
+
5. If the workflow genuinely cannot represent part of the task, use the explicit escape with uncovered scope + evidence. Covered obligations remain mandatory.
|
|
34
|
+
6. Complete only after applicable workflow obligations and objective verification resolve.
|
|
34
35
|
|
|
35
|
-
**Rule:** mandatory
|
|
36
|
+
**Rule:** mandatory applicable workflow; flexible reasoning/implementation inside it; evidence-backed escape only for uncovered scope.
|
|
36
37
|
|
|
37
38
|
Deep dive: <this-skill-dir>/references/triage-and-tiers.md
|