harness-everything 0.30.10 → 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 +24 -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/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 +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 +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/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/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/lib/advisory-text.js +2 -2
- package/scripts/lib/generated-skills.js +78 -0
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
FABLE_MAX_WORKERS,
|
|
16
16
|
validateRouterContract,
|
|
17
17
|
} = require(path.join(ROOT, 'harness-everything', 'scripts', 'router-contract.js'));
|
|
18
|
+
const { splitProfileLookupClauses } = require(tierRouter);
|
|
18
19
|
let failed = 0;
|
|
19
20
|
|
|
20
21
|
function check(condition, message) {
|
|
@@ -83,9 +84,11 @@ if (validContract(iterative, 'iterative-single')) {
|
|
|
83
84
|
check(plan.limits.maxWorkers === FABLE_MAX_WORKERS, 'workflow exposes a MAY worker planning value');
|
|
84
85
|
check(plan.requiredInvariants.includes('objective-verification'), 'iterative-single carries objective verification invariant');
|
|
85
86
|
check(plan.requiredInvariants.includes('loop-awareness'), 'iterative-single carries semantic loop-awareness obligation while numeric counts remain MAY');
|
|
86
|
-
check(plan.
|
|
87
|
+
check(plan.knowledgeSignals.includes('tdd-test'), 'test-related keywords become normalized step input signals');
|
|
88
|
+
check(plan.suggestedSkills.length === 0, 'selected topology no longer pushes a global skill list');
|
|
89
|
+
check(!plan.knowledgeSignals.some(signal => signal.includes('/') || signal.endsWith('.md')), 'knowledge signals do not directly select documents');
|
|
87
90
|
check(plan.memory.write === 'none', 'ordinary iterative work cannot write durable memory');
|
|
88
|
-
check(!plan.requiredInvariants.includes('tdd'), '
|
|
91
|
+
check(!plan.requiredInvariants.includes('tdd'), 'binding ids remain step-local rather than workflow invariants');
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
const memoryPersist = runTier('Persist this lesson as memory after resolving the checkout regression.');
|
|
@@ -94,7 +97,7 @@ if (validContract(memoryPersist, 'self-evolve persistence')) {
|
|
|
94
97
|
check(plan.strategy === 'direct-single', 'bounded explicit persistence request receives a selected workflow');
|
|
95
98
|
check(plan.memory.write === 'persist-via-self-evolve', 'explicit persistence request authorizes only self-evolve durable write');
|
|
96
99
|
check(plan.requiredInvariants.includes('memory-write-authorization'), 'durable memory route carries runtime authorization invariant');
|
|
97
|
-
check(plan.suggestedSkills.
|
|
100
|
+
check(plan.suggestedSkills.length === 0, 'memory authorization does not add a global self-evolve suggestion');
|
|
98
101
|
check(memoryPersist.contract.taskShape.observedSignals.memoryPersistenceRequested === true, 'persistence intent is recorded in task shape');
|
|
99
102
|
}
|
|
100
103
|
|
|
@@ -141,7 +144,8 @@ if (validContract(staged, 'fable-staged')) {
|
|
|
141
144
|
check(plan.parallelism.allowed === false, 'dependent stages remain sequential');
|
|
142
145
|
check(plan.verification.mode === 'cold-verifier', 'fable-staged requires cold verification');
|
|
143
146
|
check(plan.requiredInvariants.includes('isolated-worktree-before-mutation'), 'fable-staged carries mandatory isolation disposition');
|
|
144
|
-
check(plan.
|
|
147
|
+
check(plan.requiredInvariants.includes('isolated-worktree-before-mutation'), 'fable-staged keeps its worktree isolation invariant');
|
|
148
|
+
check(plan.suggestedSkills.length === 0, 'fable topology does not globally push worktree/fable skills');
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
const parallelPrompt = 'Audit the entire repository with independent read-only security, architecture, and documentation workstreams.';
|
|
@@ -218,6 +222,13 @@ if (validContract(explicitSonnect, 'explicit Fable sonnect alias')) {
|
|
|
218
222
|
|
|
219
223
|
// #290: mentioning a profile in a bounded lookup must not launch orchestration.
|
|
220
224
|
for (const prompt of [
|
|
225
|
+
'Could you please explain fable on sonnet? Return only JSON.',
|
|
226
|
+
'Please, explain fable on sonnet; return only JSON.',
|
|
227
|
+
'What is fable on opus? Return only JSON.',
|
|
228
|
+
'Explain what fable on sonnet means.Return only JSON.',
|
|
229
|
+
'Could you please explain fable on sonnet as a compact JSON record.',
|
|
230
|
+
'說明 fable on sonnet,不必啟動任何流程,請只輸出 JSON。',
|
|
231
|
+
'解釋 fable on opus,無須進入任何階段,請回傳 JSON。',
|
|
221
232
|
"Explain what 'fable on sonnect' means; return only a small JSON selection record.",
|
|
222
233
|
"Resolve fable on haiku into a profile JSON record; no orchestration or delegation.",
|
|
223
234
|
"解釋 fable on opus 的意思,只回傳 JSON。",
|
|
@@ -242,13 +253,13 @@ for (const prompt of [
|
|
|
242
253
|
const lookup = runTier(prompt);
|
|
243
254
|
const repeat = runTier(prompt);
|
|
244
255
|
if (validContract(lookup, 'bounded Fable lookup')) {
|
|
245
|
-
check(lookup.contract.classification.tier === 'tier1',
|
|
246
|
-
check(lookup.contract.workflowPlan.strategy === 'direct-single',
|
|
256
|
+
check(lookup.contract.classification.tier === 'tier1', `${prompt}: lookup remains a bounded Tier 1 operation`);
|
|
257
|
+
check(lookup.contract.workflowPlan.strategy === 'direct-single', `${prompt}: lookup selects direct-single before loading references`);
|
|
247
258
|
check(lookup.contract.workflowPlan.parallelism.allowed === false, 'lookup does not delegate');
|
|
248
|
-
check(lookup.result.stdout.includes('references/profile-lookup.md'),
|
|
249
|
-
check(!lookup.result.stdout.includes('RECOMMENDED KNOWLEDGE GUIDES'),
|
|
250
|
-
check(!lookup.result.stdout.includes('with fable-mode/scripts/model-selector.js'),
|
|
251
|
-
check(JSON.stringify(lookup.contract) === JSON.stringify(repeat.contract),
|
|
259
|
+
check(lookup.result.stdout.includes('references/profile-lookup.md'), `${prompt}: lookup selects only compact reference`);
|
|
260
|
+
check(!lookup.result.stdout.includes('RECOMMENDED KNOWLEDGE GUIDES'), `${prompt}: lookup omits unrelated keyword guides`);
|
|
261
|
+
check(!lookup.result.stdout.includes('with fable-mode/scripts/model-selector.js'), `${prompt}: lookup does not send agent to selector implementation`);
|
|
262
|
+
check(JSON.stringify(lookup.contract) === JSON.stringify(repeat.contract), `${prompt}: lookup contract is deterministic`);
|
|
252
263
|
}
|
|
253
264
|
}
|
|
254
265
|
for (const prompt of [
|
|
@@ -351,8 +362,16 @@ if (validContract(hookUnavailable, 'actionGate capability block')) {
|
|
|
351
362
|
}
|
|
352
363
|
|
|
353
364
|
const guideDedup = runTier('security audit login authentication');
|
|
354
|
-
|
|
355
|
-
|
|
365
|
+
check(guideDedup.contract.workflowPlan.knowledgeSignals.filter(signal => signal === 'security').length === 1,
|
|
366
|
+
'overlapping keywords deduplicate normalized security input signals');
|
|
367
|
+
check(!guideDedup.result.stdout.includes('security-review/SKILL.md'), 'keyword matching does not print a document path');
|
|
368
|
+
check(!guideDedup.result.stdout.includes('RECOMMENDED KNOWLEDGE GUIDES'), 'keyword matching no longer pushes guide lists');
|
|
369
|
+
|
|
370
|
+
const dottedLookupClauses = splitProfileLookupClauses('Explain what fable on sonnet means in README.md version v1.2.Return only JSON.');
|
|
371
|
+
check(dottedLookupClauses[0].includes('README.md') && dottedLookupClauses[0].includes('v1.2'),
|
|
372
|
+
'profile lookup clause splitting keeps filenames and dotted versions intact');
|
|
373
|
+
check(dottedLookupClauses.length === 2 && dottedLookupClauses[1] === 'Return only JSON',
|
|
374
|
+
'profile lookup clause splitting recognizes a no-space output clause boundary');
|
|
356
375
|
|
|
357
376
|
const repeatA = runTier('Fix this checkout bug and add a regression test.');
|
|
358
377
|
const repeatB = runTier('Fix this checkout bug and add a regression test.');
|
|
@@ -395,7 +414,7 @@ const kernelNotificationPersistFailure = spawnSync(process.execPath, [kernelRout
|
|
|
395
414
|
env: { ...process.env, HARNESS_STATE_HOME: notificationStateFile, HARNESS_WORKSPACE_ROOT: ROOT },
|
|
396
415
|
});
|
|
397
416
|
check(kernelNotificationPersistFailure.status === 0, 'host notification persistence failure stays fail-open');
|
|
398
|
-
check(kernelNotificationPersistFailure.stdout.includes('Host notification
|
|
417
|
+
check(kernelNotificationPersistFailure.stdout.includes('Host notification: no active execution contract; ignored for routing.'), 'host notification remains a routing no-op when state persistence fails');
|
|
399
418
|
check(kernelNotificationPersistFailure.stderr.includes('routing state was not persisted'), 'host notification persistence failure remains visible as a reminder');
|
|
400
419
|
fs.rmSync(notificationStateHome, { recursive: true, force: true });
|
|
401
420
|
|
|
@@ -407,7 +426,83 @@ check(kernelIterative.stdout.includes('"strategy":"iterative-single"'), 'kernel
|
|
|
407
426
|
check(kernelIterative.stdout.includes('loop-awareness:'), 'kernel prints dynamic semantic plan obligations');
|
|
408
427
|
check(kernelIterative.stdout.includes('visible-status-updates:'), 'kernel prints the user-visible status invariant');
|
|
409
428
|
check(kernelIterative.stdout.includes('USER-VISIBLE HARNESS STATUS CONTRACT (MUST)'), 'kernel prints the canonical mandatory user-visible status contract');
|
|
410
|
-
check(kernelIterative.stdout.includes('
|
|
429
|
+
check(kernelIterative.stdout.includes('KNOWLEDGE SIGNALS (STEP INPUT ONLY)'), 'kernel identifies knowledge signals as planning inputs');
|
|
430
|
+
check(!kernelIterative.stdout.includes('tdd/SKILL.md'), 'initial router output waits for an active step before disclosing a skill path');
|
|
431
|
+
check(!kernelIterative.stdout.includes('evaluate-suggestions-before-skip'), 'legacy read-every-suggestion invariant is removed');
|
|
432
|
+
|
|
433
|
+
const retainedStateHome = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-router-retained-step-'));
|
|
434
|
+
const retainedSession = 'issue297-retained-step';
|
|
435
|
+
const retainedEnv = {
|
|
436
|
+
...process.env,
|
|
437
|
+
HARNESS_STATE_HOME: retainedStateHome,
|
|
438
|
+
HARNESS_WORKSPACE_ROOT: ROOT,
|
|
439
|
+
};
|
|
440
|
+
const retainedInput = prompt => JSON.stringify({ session_id: retainedSession, cwd: ROOT, hook_event_name: 'UserPromptSubmit', prompt });
|
|
441
|
+
const initialRetainedRoute = spawnSync(process.execPath, [kernelRouter], {
|
|
442
|
+
cwd: ROOT,
|
|
443
|
+
encoding: 'utf8',
|
|
444
|
+
input: retainedInput('Fix this checkout bug and add a regression test.'),
|
|
445
|
+
env: retainedEnv,
|
|
446
|
+
});
|
|
447
|
+
check(initialRetainedRoute.status === 0, 'retained workflow fixture routes successfully');
|
|
448
|
+
check(initialRetainedRoute.stdout.includes('PLANNING STEP: decompose → compose'), 'new selected workflow exposes its planning step');
|
|
449
|
+
|
|
450
|
+
const sessionPathResult = spawnSync(process.execPath, ['-e', "const s=require('./hooks/scripts/lib/harness-state');process.stdout.write(s.getSessionDir(process.env.HARNESS_WORKSPACE_ROOT, process.env.TEST_SESSION_ID, {session_id:process.env.TEST_SESSION_ID,cwd:process.env.HARNESS_WORKSPACE_ROOT}))"], {
|
|
451
|
+
cwd: ROOT,
|
|
452
|
+
encoding: 'utf8',
|
|
453
|
+
env: { ...retainedEnv, TEST_SESSION_ID: retainedSession },
|
|
454
|
+
});
|
|
455
|
+
const retainedSessionDir = sessionPathResult.stdout;
|
|
456
|
+
const retainedController = path.join(ROOT, 'hooks', 'scripts', 'workflow-disposition.js');
|
|
457
|
+
const typedRequirements = JSON.stringify([
|
|
458
|
+
{ id: 'req-review', stepType: 'review', summary: 'Review the checkout behavior', acceptance: 'the affected behavior is understood' },
|
|
459
|
+
{ id: 'req-change', stepType: 'behavior-change', summary: 'Change the checkout behavior', acceptance: 'the requested behavior is corrected' },
|
|
460
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify the checkout behavior', acceptance: 'objective verification evidence is recorded' },
|
|
461
|
+
]);
|
|
462
|
+
const planCommand = spawnSync(process.execPath, [retainedController, 'plan', '--session-id', retainedSession,
|
|
463
|
+
'--requirements-json', typedRequirements, '--strategy', 'iterative-single', '--evidence', 'three ordered requirement steps were confirmed'], {
|
|
464
|
+
cwd: ROOT, encoding: 'utf8', env: retainedEnv,
|
|
465
|
+
});
|
|
466
|
+
check(planCommand.status === 0, 'retained workflow records typed requirements before execution');
|
|
467
|
+
const startCommand = spawnSync(process.execPath, [retainedController, 'start', '--session-id', retainedSession], {
|
|
468
|
+
cwd: ROOT, encoding: 'utf8', env: retainedEnv,
|
|
469
|
+
});
|
|
470
|
+
const startedRetained = JSON.parse(startCommand.stdout || '{}');
|
|
471
|
+
check(startCommand.status === 0 && startedRetained.activeStep?.id === 'req-review', 'retained workflow starts at its first typed step');
|
|
472
|
+
check(startedRetained.activeStepCommands?.resolveBinding.includes('--step-id "req-review"') &&
|
|
473
|
+
startedRetained.activeStepCommands?.passStep.includes('--step-id "req-review"'),
|
|
474
|
+
'start returns concrete active binding and step disposition commands');
|
|
475
|
+
const reviewCommand = spawnSync(process.execPath, [retainedController, 'step', '--session-id', retainedSession,
|
|
476
|
+
'--step-id', 'req-review', '--disposition', 'pass', '--evidence', 'the affected behavior and request were reviewed'], {
|
|
477
|
+
cwd: ROOT, encoding: 'utf8', env: retainedEnv,
|
|
478
|
+
});
|
|
479
|
+
check(reviewCommand.status === 0, 'retained workflow advances to its active behavior-change step');
|
|
480
|
+
|
|
481
|
+
const retainedWorkflowPath = path.join(retainedSessionDir, 'workflow-run.json');
|
|
482
|
+
const retainedObligationsPath = path.join(retainedSessionDir, 'workflow-obligations.json');
|
|
483
|
+
const retainedBeforeNotification = {
|
|
484
|
+
workflow: fs.readFileSync(retainedWorkflowPath, 'utf8'),
|
|
485
|
+
obligations: fs.readFileSync(retainedObligationsPath, 'utf8'),
|
|
486
|
+
};
|
|
487
|
+
const hostNotification = spawnSync(process.execPath, [kernelRouter], {
|
|
488
|
+
cwd: ROOT,
|
|
489
|
+
encoding: 'utf8',
|
|
490
|
+
input: retainedInput('<task-notification><task-id>active-step-reminder</task-id><status>completed</status></task-notification>'),
|
|
491
|
+
env: retainedEnv,
|
|
492
|
+
});
|
|
493
|
+
check(hostNotification.status === 0, 'host notification retains the active workflow');
|
|
494
|
+
check(hostNotification.stdout.includes('req-change') && hostNotification.stdout.includes('tdd/SKILL.md'),
|
|
495
|
+
'retained notification preserves only the active step and its binding');
|
|
496
|
+
check(hostNotification.stdout.includes('binding --session-id "issue297-retained-step" --step-id "req-change"') &&
|
|
497
|
+
hostNotification.stdout.includes('step --session-id "issue297-retained-step" --step-id "req-change"'),
|
|
498
|
+
'retained notification surfaces the binding and step disposition syntax');
|
|
499
|
+
check(!hostNotification.stdout.includes('verification-loop/SKILL.md'), 'retained notification hides future-step bindings');
|
|
500
|
+
check(!hostNotification.stdout.includes('ROUTER WORKFLOW PLAN (JSON)') && !hostNotification.stdout.includes('REQUIRED HARNESS INVARIANTS (SEMANTIC MUST)'),
|
|
501
|
+
'host notification avoids repeating the full workflow contract');
|
|
502
|
+
check(retainedBeforeNotification.workflow === fs.readFileSync(retainedWorkflowPath, 'utf8') &&
|
|
503
|
+
retainedBeforeNotification.obligations === fs.readFileSync(retainedObligationsPath, 'utf8'),
|
|
504
|
+
'host notification does not replace or advance workflow state');
|
|
505
|
+
fs.rmSync(retainedStateHome, { recursive: true, force: true });
|
|
411
506
|
|
|
412
507
|
for (const schemaPath of [
|
|
413
508
|
'harness-everything/schemas/router-task-shape.schema.json',
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Deterministic
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Deterministic coverage gate for positive skill evals plus an explicit
|
|
4
|
+
* classification gate for nested skills. Each positive prompt must surface the
|
|
5
|
+
* skill as a candidate step-binding id (relevance), while the router must not
|
|
6
|
+
* push canonical skill paths before an active step binds them. A new nested
|
|
7
|
+
* SKILL.md must declare whether it is parent-routed or internal; otherwise CI
|
|
8
|
+
* fails instead of silently ignoring it.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
const fs = require('fs');
|
|
@@ -22,6 +24,18 @@ const NESTED_ROUTING = new Map([
|
|
|
22
24
|
['fable-mode/fable-opus', 'parent'],
|
|
23
25
|
]);
|
|
24
26
|
|
|
27
|
+
// Skills whose positive evals prove a route shape rather than a binding: the
|
|
28
|
+
// router itself and the todo-driven multi-step strategy.
|
|
29
|
+
const ROUTE_SHAPE_SKILLS = new Map([
|
|
30
|
+
['harness-everything', output => /=> RECOMMENDED TIER: \S/.test(output)],
|
|
31
|
+
['todo-driven-workflow', output => /=> WORKFLOW STRATEGY: (?!deferred)\S+/.test(output)],
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
function candidateBindingIds(output) {
|
|
35
|
+
const line = output.match(/Candidate step binding ids: ([^\r\n]+)/);
|
|
36
|
+
return line ? line[1].split(',').map(id => id.trim()).filter(Boolean) : [];
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
function walkPositiveTasks(dir) {
|
|
26
40
|
if (!fs.existsSync(dir)) return [];
|
|
27
41
|
const files = [];
|
|
@@ -47,12 +61,6 @@ function discoverNestedSkills(skillDir) {
|
|
|
47
61
|
return found;
|
|
48
62
|
}
|
|
49
63
|
|
|
50
|
-
function expectedMarker(skill) {
|
|
51
|
-
if (skill === 'harness-everything') return 'RECOMMENDED TIER';
|
|
52
|
-
if (skill === 'todo-driven-workflow') return 'WORKFLOW STRATEGY';
|
|
53
|
-
return `${skill}/SKILL.md`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
64
|
const skillDirs = fs.readdirSync(ROOT, { withFileTypes: true })
|
|
57
65
|
.filter(entry => entry.isDirectory() && fs.existsSync(path.join(ROOT, entry.name, 'SKILL.md')))
|
|
58
66
|
.map(entry => entry.name)
|
|
@@ -62,6 +70,8 @@ let failures = 0;
|
|
|
62
70
|
let cases = 0;
|
|
63
71
|
|
|
64
72
|
const nestedOnDisk = skillDirs.flatMap(skill => discoverNestedSkills(path.join(ROOT, skill))).sort();
|
|
73
|
+
const canonicalSkillPaths = [...skillDirs, ...nestedOnDisk]
|
|
74
|
+
.map(skill => `${skill.replace(/\\/g, '/')}/SKILL.md`);
|
|
65
75
|
for (const nested of nestedOnDisk) {
|
|
66
76
|
const classification = NESTED_ROUTING.get(nested);
|
|
67
77
|
if (!classification) {
|
|
@@ -121,15 +131,34 @@ for (const skill of skillDirs) {
|
|
|
121
131
|
|
|
122
132
|
const result = spawnSync(process.execPath, [ROUTER, prompt], { cwd: ROOT, encoding: 'utf8' });
|
|
123
133
|
const output = `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
const missingPlanFields = [
|
|
135
|
+
'=> RECOMMENDED TIER:',
|
|
136
|
+
'=> WORKFLOW STRATEGY:',
|
|
137
|
+
].filter(marker => !output.includes(marker));
|
|
138
|
+
const requestedNested = nestedOnDisk
|
|
139
|
+
.filter(nested => NESTED_ROUTING.get(nested) === 'parent')
|
|
140
|
+
.filter(nested => (task.tags || []).includes(path.posix.basename(nested)));
|
|
141
|
+
const requiredParentPaths = requestedNested
|
|
142
|
+
.map(nested => `${nested.split('/')[0]}/SKILL.md`);
|
|
143
|
+
const missingParentPaths = requiredParentPaths.filter(skillPath => !output.includes(skillPath));
|
|
144
|
+
const leakedPaths = canonicalSkillPaths
|
|
145
|
+
.filter(skillPath => output.includes(skillPath) && !requiredParentPaths.includes(skillPath));
|
|
146
|
+
const routeShape = ROUTE_SHAPE_SKILLS.get(skill);
|
|
147
|
+
const relevant = routeShape ? routeShape(output) : candidateBindingIds(output).includes(skill);
|
|
148
|
+
if (result.status !== 0 || missingPlanFields.length || missingParentPaths.length || leakedPaths.length || !relevant) {
|
|
149
|
+
const reasons = [];
|
|
150
|
+
if (result.status !== 0) reasons.push(`router exited ${result.status}`);
|
|
151
|
+
if (missingPlanFields.length) reasons.push(`missing ${missingPlanFields.join(', ')}`);
|
|
152
|
+
if (!relevant) reasons.push(routeShape ? 'expected route shape not emitted' : `skill is not a candidate step binding (got: ${candidateBindingIds(output).join(', ') || 'none'})`);
|
|
153
|
+
if (missingParentPaths.length) reasons.push(`missing explicit parent route: ${missingParentPaths.join(', ')}`);
|
|
154
|
+
if (leakedPaths.length) reasons.push(`pre-step skill paths leaked: ${leakedPaths.join(', ')}`);
|
|
155
|
+
console.error(`FAIL ${path.relative(ROOT, taskFile)}: ${reasons.join('; ')}`);
|
|
127
156
|
failures++;
|
|
128
157
|
} else {
|
|
129
|
-
console.log(`PASS ${skill}/${path.basename(taskFile)}`);
|
|
158
|
+
console.log(`PASS ${skill}/${path.basename(taskFile)}: skill-specific relevance proven without pre-step path leaks`);
|
|
130
159
|
}
|
|
131
160
|
}
|
|
132
161
|
}
|
|
133
162
|
|
|
134
|
-
console.log(`\
|
|
163
|
+
console.log(`\nPositive skill-eval coverage: ${cases} case(s) checked across ${skillDirs.length} direct skills; ${nestedOnDisk.length} nested skill(s) classified.`);
|
|
135
164
|
process.exit(failures ? 1 : 0);
|
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
prepareRun,
|
|
23
23
|
validateStageGraph,
|
|
24
24
|
} = require(path.join(ROOT, 'fable-mode', 'scripts', 'workflow-plan-consumer.js'));
|
|
25
|
+
const { activeStageBindings } = require(path.join(ROOT, 'fable-mode', 'scripts', 'workflow-plan-consumer.js'));
|
|
25
26
|
const { consumeWorkspacePlan } = require(path.join(ROOT, 'multi-agent-workspace', 'scripts', 'consume-workflow-plan.js'));
|
|
26
27
|
const { pathWithinScope } = require(path.join(ROOT, 'hooks', 'scripts', 'lib', 'fable-contracts.js'));
|
|
27
28
|
|
|
@@ -70,17 +71,20 @@ const parallelStages = [
|
|
|
70
71
|
{
|
|
71
72
|
stageId: 'security', goal: 'read security state', agent: 'fable-worker-sonnet', task: 'audit security',
|
|
72
73
|
inputs: [], expectedOutputs: ['security notes'], outputPath: null, dependsOn: [], writeSet: [],
|
|
74
|
+
requiredBindings: ['unknown-security-check'],
|
|
73
75
|
checkCommand: 'node --test security.test.js', passCondition: 'exit 0',
|
|
74
76
|
},
|
|
75
77
|
{
|
|
76
78
|
stageId: 'architecture', goal: 'read architecture state', agent: 'fable-worker-sonnet', task: 'audit architecture',
|
|
77
79
|
inputs: [], expectedOutputs: ['architecture notes'], outputPath: null, dependsOn: [], writeSet: [],
|
|
80
|
+
optionalBindings: [{ id: 'architecture-reference', path: 'docs/architecture.md' }],
|
|
78
81
|
checkCommand: 'node --test architecture.test.js', passCondition: 'exit 0',
|
|
79
82
|
},
|
|
80
83
|
{
|
|
81
84
|
stageId: 'synthesis', goal: 'synthesize results', agent: 'fable-worker-sonnet', task: 'write synthesis',
|
|
82
85
|
inputs: [], expectedOutputs: ['summary'], outputPath: 'reports/summary.md', dependsOn: ['security', 'architecture'],
|
|
83
|
-
writeSet: ['reports/summary.md'],
|
|
86
|
+
writeSet: ['reports/summary.md'], requiredBindings: [{ id: 'verification-loop', path: 'verification-loop/SKILL.md' }],
|
|
87
|
+
checkCommand: 'node --test synthesis.test.js', passCondition: 'exit 0',
|
|
84
88
|
},
|
|
85
89
|
];
|
|
86
90
|
|
|
@@ -95,6 +99,30 @@ check(JSON.stringify(runA.execution.batches) === JSON.stringify([['architecture'
|
|
|
95
99
|
check(runA.execution.limits.maxWorkers === 4, 'run manifest retains maxWorkers as an advisory planning hint');
|
|
96
100
|
check(fs.existsSync(path.join(runA.runRoot, 'contracts', 'security.json')), 'run-scoped stage contract is written');
|
|
97
101
|
check(fs.existsSync(path.join(runA.runRoot, 'contracts', 'synthesis.json')), 'downstream stage has its own contract');
|
|
102
|
+
const initialActiveStages = activeStageBindings(runA.runRoot);
|
|
103
|
+
check(initialActiveStages.map(stage => stage.stageId).join(',') === 'security,architecture', 'only dependency-ready Fable stages expose bindings');
|
|
104
|
+
check(initialActiveStages[0].requiredBindings[0].path === null &&
|
|
105
|
+
initialActiveStages[0].requiredBindings[0].status === 'pending', 'unknown required binding remains visible and unresolved');
|
|
106
|
+
check(initialActiveStages[1].optionalBindings[0].path === 'docs/architecture.md' &&
|
|
107
|
+
initialActiveStages[1].optionalBindings[0].status === 'pending', 'active optional reference path appears only on its ready stage');
|
|
108
|
+
check(!JSON.stringify(initialActiveStages).includes('verification-loop/SKILL.md'), 'future-stage binding paths stay hidden until dependencies pass');
|
|
109
|
+
for (const stageId of ['security', 'architecture']) {
|
|
110
|
+
const file = path.join(runA.runRoot, 'contracts', `${stageId}.json`);
|
|
111
|
+
const contract = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
112
|
+
contract.status = 'pass';
|
|
113
|
+
fs.writeFileSync(file, JSON.stringify(contract));
|
|
114
|
+
}
|
|
115
|
+
const unresolvedDependencies = activeStageBindings(runA.runRoot);
|
|
116
|
+
check(!unresolvedDependencies.some(stage => stage.stageId === 'synthesis'), 'a stage marked pass with unresolved bindings does not activate dependents');
|
|
117
|
+
for (const stageId of ['security', 'architecture']) {
|
|
118
|
+
const file = path.join(runA.runRoot, 'contracts', `${stageId}.json`);
|
|
119
|
+
const contract = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
120
|
+
for (const binding of [...contract.requiredBindings, ...contract.optionalBindings]) binding.status = 'loaded';
|
|
121
|
+
fs.writeFileSync(file, JSON.stringify(contract));
|
|
122
|
+
}
|
|
123
|
+
const nextActiveStages = activeStageBindings(runA.runRoot);
|
|
124
|
+
check(nextActiveStages.map(stage => stage.stageId).join(',') === 'synthesis', 'downstream bindings appear only after every dependency passes');
|
|
125
|
+
check(nextActiveStages[0].requiredBindings[0].path === 'verification-loop/SKILL.md', 'current stage receives only its own declared binding path');
|
|
98
126
|
|
|
99
127
|
const runB = prepareRun({
|
|
100
128
|
routerContract: parallelContract,
|
|
@@ -143,6 +171,18 @@ expectThrow(() => validateStageGraph([
|
|
|
143
171
|
{ stageId: 'b', goal: 'b', agent: 'a', task: 'b', dependsOn: ['a'], writeSet: [], inputs: [], expectedOutputs: [] },
|
|
144
172
|
]), /cyclic/, 'dependency cycle is rejected');
|
|
145
173
|
|
|
174
|
+
expectThrow(() => validateStageGraph([
|
|
175
|
+
{ stageId: 'unsafe-binding', goal: 'x', agent: 'a', task: 'x', dependsOn: [], writeSet: [], requiredBindings: [{ id: 'escaped', path: '../outside.md' }] },
|
|
176
|
+
]), /repository-relative|escapes/, 'binding paths reject traversal outside the repository');
|
|
177
|
+
|
|
178
|
+
expectThrow(() => validateStageGraph([
|
|
179
|
+
{ stageId: 'drive-binding', goal: 'x', agent: 'a', task: 'x', dependsOn: [], writeSet: [], requiredBindings: [{ id: 'drive', path: 'C:outside.md' }] },
|
|
180
|
+
]), /repository-relative|escapes/, 'binding paths reject Windows drive-relative names');
|
|
181
|
+
|
|
182
|
+
expectThrow(() => validateStageGraph([
|
|
183
|
+
{ stageId: 'duplicate-binding', goal: 'x', agent: 'a', task: 'x', dependsOn: [], writeSet: [], requiredBindings: ['same'], optionalBindings: ['same'] },
|
|
184
|
+
]), /both required and optional/, 'a stage binding cannot be both required and optional');
|
|
185
|
+
|
|
146
186
|
expectThrow(() => prepareRun({
|
|
147
187
|
routerContract: parallelContract,
|
|
148
188
|
workspaceRoot: workspace,
|
package/docs/architecture.md
CHANGED
|
@@ -10,7 +10,7 @@ The minimal kernel establishes:
|
|
|
10
10
|
|
|
11
11
|
- scope/tier before mutation,
|
|
12
12
|
- a selected workflow topology as a semantic execution contract when task evidence supports one,
|
|
13
|
-
-
|
|
13
|
+
- ordered requirement decomposition and step-scoped knowledge bindings,
|
|
14
14
|
- objective evidence before completion,
|
|
15
15
|
- bounded re-plan/recovery after repeated failure,
|
|
16
16
|
- explicit warnings/degraded/escape evidence instead of silent workflow deletion;
|
|
@@ -18,7 +18,7 @@ The minimal kernel establishes:
|
|
|
18
18
|
|
|
19
19
|
> **Contract-first lifecycle; flexible reasoning/implementation and execution.**
|
|
20
20
|
>
|
|
21
|
-
>
|
|
21
|
+
> Active required bindings and selected-topology obligations are semantic MUSTs; keyword signals are planning inputs, and unrelated future bindings stay undisclosed.
|
|
22
22
|
|
|
23
23
|
This avoids both extremes: no universal Tier-2/Tier-3 pipeline and no maze of self-deadlocking cognitive gates.
|
|
24
24
|
|
|
@@ -31,10 +31,9 @@ flowchart TD
|
|
|
31
31
|
P --> A{Strategy selected?}
|
|
32
32
|
A -- No / deferred --> D[Preserve invariants<br/>choose smallest justified workflow]
|
|
33
33
|
A -- Yes --> C[ACTIVE workflow contract]
|
|
34
|
-
C -->
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
E --> X
|
|
34
|
+
C --> D[Decompose ordered requirements]
|
|
35
|
+
D --> B[Bind skills/references per step]
|
|
36
|
+
B --> X[Execute active step/stage]
|
|
38
37
|
X --> V{Required stage/check resolved?}
|
|
39
38
|
V -- Yes --> Done[Evidence-backed completion]
|
|
40
39
|
V -- No --> R[Diagnose / bounded re-plan]
|
|
@@ -49,7 +48,8 @@ flowchart TD
|
|
|
49
48
|
- tier + rationale,
|
|
50
49
|
- selected/deferred execution strategy,
|
|
51
50
|
- required invariants,
|
|
52
|
-
-
|
|
51
|
+
- normalized knowledge signals for planning, not document selection,
|
|
52
|
+
- bindings only for the active step or dependency-ready Fable stage,
|
|
53
53
|
- the internal routing checkpoint,
|
|
54
54
|
- the semantic workflow contract.
|
|
55
55
|
|
|
@@ -74,9 +74,9 @@ Use nested evidence bullets when multiple items would otherwise become one dense
|
|
|
74
74
|
|
|
75
75
|
This is a semantic contract, not another cognitive lock. Hook-capable hosts may observe or remind about compliance; instruction-only hosts still receive the MUST contract without pretending they can mechanically enforce it.
|
|
76
76
|
|
|
77
|
-
###
|
|
77
|
+
### Step-scoped knowledge vs. topology execution
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
Keyword/domain matches are planning signals, not skill/reference paths. Requirement composition declares bindings on ordered steps; only active bindings are disclosed. A required skill binding MUST be read and its applicable core contract followed before the step passes. Unknown/unavailable paths remain visible and unresolved, while unrelated and future-step knowledge is not preloaded.
|
|
80
80
|
|
|
81
81
|
The selected topology is a semantic lifecycle contract, not a persistent execution lock. Required obligations MUST resolve; implementation tactics MAY adapt.
|
|
82
82
|
|
|
@@ -89,7 +89,7 @@ For selected Fable topologies on Claude:
|
|
|
89
89
|
- `workflow-gate.js` observes supported mutation/worktree/Fable state and emits reminders;
|
|
90
90
|
- Fable owns stage contracts, `dependsOn`/`writeSet`, validated execution batches, objective per-stage checks, synthesis, cold verification, and bounded re-plans;
|
|
91
91
|
- `workflow-stop-gate.js` reports unresolved stage/verification evidence without rejecting Stop;
|
|
92
|
-
- `workflow-disposition.js` records explicit lifecycle/audit decisions; numeric budgets are no longer authoritative control state.
|
|
92
|
+
- `workflow-disposition.js` records explicit lifecycle/audit decisions and active step/stage binding dispositions; numeric budgets are no longer authoritative control state.
|
|
93
93
|
|
|
94
94
|
Other topologies use their own applicable mechanisms: MAY-level loop-awareness planning hints, regular verification stop-gates, action gates, and task-specific skills. Mechanism coverage is not assumed identical across hosts.
|
|
95
95
|
|
|
@@ -7,7 +7,7 @@ Harness deliberately separates **semantic obligation strength** from **mechanica
|
|
|
7
7
|
| Mechanism / obligation | Semantic strength | Runtime behavior | Hard block? |
|
|
8
8
|
|---|---|---|---|
|
|
9
9
|
| Workflow routing / selected topology | **MUST** resolve required obligations | Observe/remind about unresolved lifecycle evidence | No |
|
|
10
|
-
|
|
|
10
|
+
| Active required skill/reference binding | **MUST** load/resolve before its step passes; applicable skill core contract **MUST** be followed | Step/stage contract records declared binding dispositions; active paths only | No |
|
|
11
11
|
| Iteration/revision/replan/worker numbers | **MAY** guide planning | Planning hints only | No |
|
|
12
12
|
| Verification before claim | **MUST** | Stop/hooks remind when evidence is missing | No |
|
|
13
13
|
| Boundary guard / huge-noisy context | **SHOULD** narrow reads/searches | Warn | No |
|
|
@@ -4,7 +4,7 @@ Harness coordinates independently useful skills through a small workflow runtime
|
|
|
4
4
|
|
|
5
5
|
The preferred shape is:
|
|
6
6
|
|
|
7
|
-
> **
|
|
7
|
+
> **step-scoped knowledge bindings + local skill autonomy + narrow runtime mechanisms**
|
|
8
8
|
|
|
9
9
|
Platform-specific enforcement claims remain bounded by [platform-capabilities.md](platform-capabilities.md).
|
|
10
10
|
|
|
@@ -19,7 +19,7 @@ A skill should answer:
|
|
|
19
19
|
3. **What workflow/mechanism do I expose?** A local flow, verifier, script, gate, or stable result when useful.
|
|
20
20
|
4. **What evidence means I am done?** A check, artifact, or explicit blocked state.
|
|
21
21
|
|
|
22
|
-
The router
|
|
22
|
+
The router emits normalized keyword/domain signals for requirement composition; these signals are not document selectors. Ordered requirement steps declare the skills/references they need, and only the active step or dependency-ready Fable stage exposes those paths. Separately, the router selects one smallest sufficient outer topology. **The selected topology governs the run.**
|
|
23
23
|
|
|
24
24
|
## Why Not One Global Skill Sequence?
|
|
25
25
|
|
|
@@ -33,16 +33,15 @@ A universal TODO/TDD/Fable sequence would:
|
|
|
33
33
|
So Harness separates **topology** from **capabilities/domain skills**:
|
|
34
34
|
|
|
35
35
|
```text
|
|
36
|
-
tier / task shape
|
|
37
36
|
+
|
|
38
37
|
selected execution topology
|
|
39
38
|
+
|
|
40
|
-
|
|
39
|
+
active-step knowledge bindings
|
|
41
40
|
+
|
|
42
41
|
required invariants/gates
|
|
43
42
|
```
|
|
44
43
|
|
|
45
|
-
The topology is the lifecycle contract. Skills are
|
|
44
|
+
The topology is the lifecycle contract. Skills and references are bound to the requirement steps that consume them.
|
|
46
45
|
|
|
47
46
|
## Mechanisms Over Prose
|
|
48
47
|
|
|
@@ -56,8 +55,9 @@ tier: 2
|
|
|
56
55
|
strategy: iterative-single
|
|
57
56
|
workflow_state: active
|
|
58
57
|
required: scope-lock, objective-verification, loop-awareness
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
knowledge_signals: test-related
|
|
59
|
+
active_step: behavior-change
|
|
60
|
+
required_bindings: tdd/SKILL.md
|
|
61
61
|
escape_policy: workflow-uncovered-scope-only
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -81,18 +81,19 @@ Every skill should retain:
|
|
|
81
81
|
- explicit neighbor links rather than hidden dependencies;
|
|
82
82
|
- blocked/exit discipline.
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
An active required binding exists so the agent can inspect the local contract without loading the entire ecosystem. If the skill applies to that step, follow its core contract; later or unrelated bindings remain undisclosed.
|
|
85
85
|
|
|
86
86
|
## Coordination Patterns
|
|
87
87
|
|
|
88
88
|
### Router Decision
|
|
89
89
|
|
|
90
|
-
`harness-everything/scripts/kernel-router.js` emits tier, strategy, invariants,
|
|
90
|
+
`harness-everything/scripts/kernel-router.js` emits tier, strategy, invariants, normalized knowledge signals, and an execution contract.
|
|
91
91
|
|
|
92
|
-
-
|
|
92
|
+
- Knowledge signals: planning inputs only; they never choose paths.
|
|
93
|
+
- Active bindings: declare required/optional skill/reference ids and paths on ordered steps; future-step bindings remain hidden.
|
|
93
94
|
- Selected topology: execute to resolution (`active` → verified/satisfied, blocked, or explicit evidence-backed escape).
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
Required bindings must resolve before the current step/stage passes. Unknown/unavailable bindings stay visible; hooks and Stop remain fail-open.
|
|
96
97
|
|
|
97
98
|
### Fable Stage Contracts
|
|
98
99
|
|
package/docs/openai-plugin.md
CHANGED
|
@@ -28,7 +28,7 @@ flowchart TD
|
|
|
28
28
|
X[Stop hook] --> V
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
The local OpenAI adapter
|
|
31
|
+
The local OpenAI adapter observes cross-cutting invariants through the lifecycle events that it packages. Required bindings are declared per active step/stage; the selected topology is mandatory with scoped, evidence-backed escape. The [workflow runtime](workflow-runtime.md) documents active binding visibility, pending/running/satisfied/blocked state, Fable entry, worktree isolation, completion evidence, and degradation limits. Tool enforcement is limited to the host tool names declared in `hooks/hooks.json`.
|
|
32
32
|
|
|
33
33
|
## Package layout
|
|
34
34
|
|
package/docs/philosophy.md
CHANGED
|
@@ -10,7 +10,7 @@ The mechanism/enforcement boundary remains platform-specific; see [Platform Capa
|
|
|
10
10
|
|
|
11
11
|
1. **Reasoning autonomy remains high.** The model chooses implementation technique, tools, decomposition details, and tactics inside the selected workflow.
|
|
12
12
|
2. **Selected lifecycle obligations are contracts, not advice.** Once a topology is selected, its required invariants, stages, checks, synthesis barriers, and verification obligations are semantic MUSTs. The model remains free to decide how to satisfy them.
|
|
13
|
-
3. **
|
|
13
|
+
3. **Knowledge is step-scoped.** Normalized keyword signals help requirement composition but do not select documents. Ordered steps declare required/optional skill or reference bindings; active required bindings MUST be read and their applicable core contracts followed before the step passes.
|
|
14
14
|
4. **Escape is conditional, not default.** If the selected workflow genuinely cannot represent part of the task, record the uncovered scope and evidence, then allow model-defined handling only for that uncovered portion.
|
|
15
15
|
5. **Mechanisms do not define obligation strength.** Hooks/plugins may observe, remind, or mechanically block where the host supports them, but a missing blocking mechanism does not downgrade a semantic MUST to optional advice.
|
|
16
16
|
|
|
@@ -26,7 +26,7 @@ Harness uses three semantic strengths. Recommendation-style language is the last
|
|
|
26
26
|
|
|
27
27
|
| Strength | Meaning | Typical use |
|
|
28
28
|
|---|---|---|
|
|
29
|
-
| **MUST** | Skipping the obligation violates the Harness semantic contract. Host enforcement may still be reminder-only. | routing,
|
|
29
|
+
| **MUST** | Skipping the obligation violates the Harness semantic contract. Host enforcement may still be reminder-only. | routing, active required binding resolution, applicable skill core contracts, selected-topology obligations, verification-before-claim, status, scope/safety/authorization evidence |
|
|
30
30
|
| **SHOULD** | The default is expected, but a concrete evidence-based exception is legitimate. | ordinary isolation, targeted editing, splitting mixed commit concerns, context-size/noisy-search discipline |
|
|
31
31
|
| **MAY** | Optional optimization/capability. Omitting it needs no exception unless another contract selected it. | numeric planning hints, optional ensemble selection, extra deep references, opportunistic parallelism |
|
|
32
32
|
|
|
@@ -37,8 +37,9 @@ The repository-wide classification for #217 is:
|
|
|
37
37
|
| Obligation | Strength |
|
|
38
38
|
|---|---|
|
|
39
39
|
| Route before software/project execution | **MUST** |
|
|
40
|
-
|
|
|
41
|
-
| Follow an
|
|
40
|
+
| Resolve active required skill/reference bindings before their step | **MUST** |
|
|
41
|
+
| Follow an active skill binding's applicable core contract | **MUST** |
|
|
42
|
+
| Load unrelated keyword-matched documents or future-step bindings | Not required |
|
|
42
43
|
| Resolve selected-topology invariants/stages/checks/synthesis/verification | **MUST** |
|
|
43
44
|
| Choose tools, implementation technique, decomposition details, and local tactics | **MAY** within the MUST contract |
|
|
44
45
|
| Verify before claiming completion | **MUST** |
|
|
@@ -67,7 +68,7 @@ The router chooses the **smallest sufficient topology**:
|
|
|
67
68
|
- `fable-parallel` only for validated independent workstreams;
|
|
68
69
|
- `fable-multi-agent-workspace` when persistent roles/handoffs/memory are materially needed.
|
|
69
70
|
|
|
70
|
-
Domain skills
|
|
71
|
+
Domain skills and references are conditional on the needs of each requirement step. The contract says “execute the selected lifecycle”, not “execute every skill in the repository.” Unknown required bindings remain visible and unresolved; they do not turn workflow hooks into a persistent lock.
|
|
71
72
|
|
|
72
73
|
## The 4 Pillars of AI Model Guidance
|
|
73
74
|
|
|
@@ -81,7 +82,7 @@ Circuit breakers, action gates, workflow gates, scope guards, and completion gat
|
|
|
81
82
|
|
|
82
83
|
### 3. Context Preservation — Progressive Disclosure
|
|
83
84
|
|
|
84
|
-
**SHOULD** load the smallest useful context.
|
|
85
|
+
**SHOULD** load the smallest useful context. An active required skill's complete entry **MUST** be read and followed; optional bindings resolve as loaded or not-needed. Future-step bindings remain undisclosed until active. Machine-visible workflow state SHOULD carry lifecycle facts instead of repeating large prompt blocks.
|
|
85
86
|
|
|
86
87
|
### 4. Self-Evolution — Learn from Workflow Evidence
|
|
87
88
|
|