opencode-wait-a-minute 0.0.0-stage → 1.1.0-rc.8
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/LICENSE +21 -0
- package/README.md +318 -2
- package/index.js +1836 -0
- package/package.json +70 -4
- package/skills/codebase-design/SKILL.md +114 -0
- package/skills/design-contract/SKILL.md +27 -0
- package/skills/design-critique/SKILL.md +19 -0
- package/skills/design-system/SKILL.md +17 -0
- package/skills/design-system/references/image-analysis.md +14 -0
- package/skills/design-system/references/tokens.md +32 -0
- package/skills/design-system/references/ui-rules.md +14 -0
- package/skills/diagnosing-bugs/SKILL.md +138 -0
- package/skills/frontend-design/SKILL.md +36 -0
- package/skills/handoff/SKILL.md +16 -0
- package/skills/improve-codebase-architecture/SKILL.md +71 -0
- package/skills/linter-negative/SKILL.md +74 -0
- package/skills/no-slop-ui/SKILL.md +26 -0
- package/skills/prototype/SKILL.md +26 -0
- package/skills/registry.json +44087 -0
- package/skills/tastemaker/SKILL.md +8 -0
- package/skills/tastemaker/references/tastemaker.md +14 -0
- package/skills/tdd/SKILL.md +38 -0
- package/skills/visual-review/SKILL.md +103 -0
- package/skills/visual-review/references/review-gates.md +42 -0
- package/skills/visual-review/references/surface-archetypes.md +37 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/writing-for-agents/SKILL.md +81 -0
- package/src/cognition/cognition-store.js +251 -0
- package/src/cognition/cognitive-state.js +126 -0
- package/src/cognition/hypothesis-manager.js +113 -0
- package/src/cognition/observation-engine.js +112 -0
- package/src/cognition/observation-provenance.js +268 -0
- package/src/context/active-context-boundary.js +68 -0
- package/src/context/assembly.js +494 -0
- package/src/context/assessment-rules.js +55 -0
- package/src/context/capsule-staleness.js +267 -0
- package/src/context/capsules.js +2 -0
- package/src/context/compare-array.js +98 -0
- package/src/context/compare-object.js +89 -0
- package/src/context/compare-values.js +42 -0
- package/src/context/context-assembly-contract.js +71 -0
- package/src/context/context-budget-manager.js +232 -0
- package/src/context/context-builder.js +49 -0
- package/src/context/context-cache.js +55 -0
- package/src/context/context-capture.js +243 -0
- package/src/context/context-classification.js +199 -0
- package/src/context/context-compaction.js +179 -0
- package/src/context/context-decision-audit.js +267 -0
- package/src/context/context-demotion.js +173 -0
- package/src/context/context-dictionary.js +106 -0
- package/src/context/context-drift-detection.js +225 -0
- package/src/context/context-evaluation.js +374 -0
- package/src/context/context-event-ingress.js +100 -0
- package/src/context/context-event-normalization.js +224 -0
- package/src/context/context-feedback-loop.js +216 -0
- package/src/context/context-freshness.js +21 -0
- package/src/context/context-graph-builder.js +71 -0
- package/src/context/context-graph.js +451 -0
- package/src/context/context-history-relevance.js +321 -0
- package/src/context/context-interception.js +145 -0
- package/src/context/context-invalidation-events.js +154 -0
- package/src/context/context-lifecycle.js +170 -0
- package/src/context/context-loop-prevention.js +347 -0
- package/src/context/context-manager.js +340 -0
- package/src/context/context-memory-layers.js +265 -0
- package/src/context/context-minimality.js +166 -0
- package/src/context/context-normalization.js +130 -0
- package/src/context/context-optimization-metrics.js +434 -0
- package/src/context/context-output-policy.js +135 -0
- package/src/context/context-pack-authority.js +157 -0
- package/src/context/context-promotion.js +172 -0
- package/src/context/context-query-contract.js +101 -0
- package/src/context/context-ranking.js +164 -0
- package/src/context/context-rate-distortion.js +261 -0
- package/src/context/context-recovery.js +209 -0
- package/src/context/context-retrieval-integration.js +60 -0
- package/src/context/context-retrieval.js +93 -0
- package/src/context/context-router.js +457 -0
- package/src/context/context-routing-evaluation.js +331 -0
- package/src/context/context-routing-observability.js +296 -0
- package/src/context/context-routing-shadow.js +237 -0
- package/src/context/context-scope.js +181 -0
- package/src/context/context-snapshot.js +226 -0
- package/src/context/context-source-registry.js +95 -0
- package/src/context/context-sufficiency-gate.js +120 -0
- package/src/context/context-sufficiency-oracle.js +167 -0
- package/src/context/context-telemetry.js +176 -0
- package/src/context/context.js +633 -0
- package/src/context/dependency-closure.js +246 -0
- package/src/context/page-fault-tracker.js +275 -0
- package/src/context/prompt-engine.js +74 -0
- package/src/context/runtime-context-graph.js +260 -0
- package/src/context/selection.js +2 -0
- package/src/context/session.js +2 -0
- package/src/context/sufficiency-contract.js +300 -0
- package/src/context/tokenizer.js +17 -0
- package/src/context-benchmark-router.mjs +125 -0
- package/src/evidence/evidence-engine.js +161 -0
- package/src/evidence/evidence-factory.js +3 -0
- package/src/evidence/evidence-freshness.js +463 -0
- package/src/evidence/evidence-gap.js +76 -0
- package/src/evidence/evidence-lineage.js +525 -0
- package/src/evidence/evidence.js +136 -0
- package/src/evidence/freshness-validation.js +187 -0
- package/src/evidence/lineage.js +4 -0
- package/src/execution/action-evaluation.js +179 -0
- package/src/execution/circuit-breaker.js +70 -0
- package/src/execution/claim-interception.js +63 -0
- package/src/execution/execution-engine.js +258 -0
- package/src/execution/execution-intent.js +69 -0
- package/src/execution/execution-state.js +89 -0
- package/src/execution/formatting.js +60 -0
- package/src/execution/l1-sanitize.js +16 -0
- package/src/execution/l2-resource-guards.js +19 -0
- package/src/execution/l3-invariant-checker.js +5 -0
- package/src/execution/l4-containment.js +6 -0
- package/src/execution/runtime-guards.js +106 -0
- package/src/execution/strategy-identity.js +162 -0
- package/src/execution/tool-interceptor.js +7 -0
- package/src/execution/tool-result-classifier.js +63 -0
- package/src/integration/canary-deploy.js +143 -0
- package/src/integration/collector.js +59 -0
- package/src/integration/error-recovery.js +6 -0
- package/src/integration/event-bus.js +66 -0
- package/src/integration/health-checker.js +57 -0
- package/src/integration/logger.js +39 -0
- package/src/integration/message-handler.js +611 -0
- package/src/integration/opencode-db.js +153 -0
- package/src/integration/orchestration.js +419 -0
- package/src/integration/part-provenance.js +65 -0
- package/src/integration/rate-limiter.js +55 -0
- package/src/integration/release-gate.js +336 -0
- package/src/integration/rollback-manager.js +164 -0
- package/src/integration/router-adapter.js +269 -0
- package/src/integration/telemetry.js +45 -0
- package/src/integration/update-checker.js +10 -0
- package/src/persistence/event-sourcing.js +75 -0
- package/src/persistence/memory.js +501 -0
- package/src/persistence/migration-runner.js +16 -0
- package/src/persistence/persistence-manager.js +39 -0
- package/src/persistence/transaction-log.js +157 -0
- package/src/policy/action-capabilities.js +116 -0
- package/src/policy/action-policy.js +4 -0
- package/src/policy/assessment-engine.js +104 -0
- package/src/policy/assumption-tracking.js +66 -0
- package/src/policy/budget-enforcer.js +7 -0
- package/src/policy/config-validator.js +102 -0
- package/src/policy/execution-assessment.js +142 -0
- package/src/policy/failure-classification.js +5 -0
- package/src/policy/feature-flags.js +65 -0
- package/src/policy/governance-enforcement.js +118 -0
- package/src/policy/locale.js +15 -0
- package/src/policy/multi-signer.js +151 -0
- package/src/policy/policy-state-machine.js +97 -0
- package/src/policy/ponytail-constraint.js +38 -0
- package/src/policy/project-inspector.js +93 -0
- package/src/policy/query-builder.js +14 -0
- package/src/policy/request-classifier.js +79 -0
- package/src/policy/risk-engine.js +266 -0
- package/src/policy/scope-enforcement.js +140 -0
- package/src/policy/secret-scanner.js +68 -0
- package/src/policy/skill-routing.js +104 -0
- package/src/policy/strategy-capabilities.js +211 -0
- package/src/policy/strategy.js +6 -0
- package/src/policy/uncertainty.js +117 -0
- package/src/shared/container.js +44 -0
- package/src/shared/decision-types.js +24 -0
- package/src/shared/simple-test.js +160 -0
- package/src/shared/wam-events.js +52 -0
- package/src/skills/engine.js +1705 -0
- package/src/skills/skill-registry.js +19 -0
- package/src/skills/skill-routing.js +340 -0
- package/src/state/lifecycle-manager.js +113 -0
- package/src/state/operational-state.js +198 -0
- package/src/state/projections.js +47 -0
- package/src/state/replay-engine.js +5 -0
- package/src/state/requirement-ownership.js +20 -0
- package/src/state/requirement-state.js +152 -0
- package/src/state/run-event-identity.js +172 -0
- package/src/state/state-engine.js +19 -0
- package/src/state/state-machine.js +119 -0
- package/src/state/state-schema.js +318 -0
- package/src/state/state-store.js +335 -0
- package/src/state/state-transition.js +84 -0
- package/src/state/task-dependencies.js +287 -0
- package/src/state/task-execution.js +217 -0
- package/src/state/task-lifecycle.js +59 -0
- package/src/state/task-runs.js +361 -0
- package/src/state/task-state-file.js +86 -0
- package/src/state/task-state.js +41 -0
- package/src/state/task-status.js +54 -0
- package/src/state/task-store.js +48 -0
- package/src/state/wam-state.js +148 -0
- package/src/verification/completion-gate-simple.js +8 -0
- package/src/verification/completion-gate.js +183 -0
- package/src/verification/contract.js +9 -0
- package/src/verification/contradiction-resolution.js +255 -0
- package/src/verification/diagnostic-engine.js +659 -0
- package/src/verification/false-completion-prevention.js +100 -0
- package/src/verification/hook-validator.js +6 -0
- package/src/verification/requirements.js +6 -0
- package/src/verification/verification-context.js +162 -0
- package/src/verification/verification-lifecycle.js +66 -0
- package/src/verification/verification-model.js +89 -0
- package/src/verification/verification-persistence.js +129 -0
- package/src/verification/verification-policy.js +127 -0
- package/src/verification/verification.js +415 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wam-state.js — Wait-a-Minute session state management.
|
|
3
|
+
*
|
|
4
|
+
* Provides CRUD for WAM (Wait-a-Minute) session state persisted to disk,
|
|
5
|
+
* session resumption, and schema migration utilities.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs/promises';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
// ── Constants ────────────────────────────────────────────────
|
|
12
|
+
export const SCHEMA_VERSION = 1;
|
|
13
|
+
export const STATE_FILENAME = 'wam-state.json';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns the current schema version number.
|
|
17
|
+
* @param {object} [state] — optional state object to inspect for its version
|
|
18
|
+
* @returns {number} schema version
|
|
19
|
+
*/
|
|
20
|
+
export function getSchemaVersion(state) {
|
|
21
|
+
if (state && typeof state === 'object' && '_schemaVersion' in state) {
|
|
22
|
+
return state._schemaVersion;
|
|
23
|
+
}
|
|
24
|
+
return SCHEMA_VERSION;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates a new WAM state object.
|
|
29
|
+
* @param {string} taskId
|
|
30
|
+
* @param {object} [requirements={}]
|
|
31
|
+
* @param {object} [context={}]
|
|
32
|
+
* @param {object} [evidence={}]
|
|
33
|
+
* @returns {object} the created state
|
|
34
|
+
*/
|
|
35
|
+
export function createWamState(taskId, requirements = {}, context = {}, evidence = {}) {
|
|
36
|
+
const now = new Date().toISOString();
|
|
37
|
+
return {
|
|
38
|
+
_schemaVersion: SCHEMA_VERSION,
|
|
39
|
+
taskId,
|
|
40
|
+
createdAt: now,
|
|
41
|
+
updatedAt: now,
|
|
42
|
+
requirements,
|
|
43
|
+
context,
|
|
44
|
+
evidence,
|
|
45
|
+
status: 'active',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Saves WAM state to disk.
|
|
51
|
+
* @param {string} taskId
|
|
52
|
+
* @param {object} state
|
|
53
|
+
* @param {string} rootDir
|
|
54
|
+
* @returns {Promise<string>} resolved file path
|
|
55
|
+
*/
|
|
56
|
+
export async function saveWamState(taskId, state, rootDir) {
|
|
57
|
+
const dir = path.join(rootDir, taskId);
|
|
58
|
+
await fs.mkdir(dir, { recursive: true });
|
|
59
|
+
state.updatedAt = new Date().toISOString();
|
|
60
|
+
const filePath = path.join(dir, STATE_FILENAME);
|
|
61
|
+
await fs.writeFile(filePath, JSON.stringify(state, null, 2));
|
|
62
|
+
return filePath;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Loads WAM state from disk.
|
|
67
|
+
* @param {string} taskId
|
|
68
|
+
* @param {string} rootDir
|
|
69
|
+
* @returns {Promise<object|null>} the loaded state or null if not found
|
|
70
|
+
*/
|
|
71
|
+
export async function loadWamState(taskId, rootDir) {
|
|
72
|
+
const filePath = path.join(rootDir, taskId, STATE_FILENAME);
|
|
73
|
+
try {
|
|
74
|
+
await fs.access(filePath);
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
79
|
+
return JSON.parse(raw);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resumes a session from a loaded WAM state.
|
|
84
|
+
* Returns a resume descriptor with metadata about the session to continue.
|
|
85
|
+
* @param {object} wamState
|
|
86
|
+
* @returns {object} resume descriptor
|
|
87
|
+
*/
|
|
88
|
+
export function resumeSession(wamState) {
|
|
89
|
+
if (!wamState || typeof wamState !== 'object') {
|
|
90
|
+
return {
|
|
91
|
+
resumable: false,
|
|
92
|
+
reason: 'No valid state provided',
|
|
93
|
+
taskId: null,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (wamState.status === 'completed') {
|
|
98
|
+
return {
|
|
99
|
+
resumable: false,
|
|
100
|
+
reason: 'Session already completed',
|
|
101
|
+
taskId: wamState.taskId ?? null,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
resumable: true,
|
|
107
|
+
taskId: wamState.taskId ?? null,
|
|
108
|
+
status: wamState.status ?? 'unknown',
|
|
109
|
+
createdAt: wamState.createdAt,
|
|
110
|
+
updatedAt: wamState.updatedAt,
|
|
111
|
+
schemaVersion: getSchemaVersion(wamState),
|
|
112
|
+
hasRequirements: Boolean(wamState.requirements && Object.keys(wamState.requirements).length > 0),
|
|
113
|
+
hasContext: Boolean(wamState.context && Object.keys(wamState.context).length > 0),
|
|
114
|
+
hasEvidence: Boolean(wamState.evidence && Object.keys(wamState.evidence).length > 0),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Migrates an old-format WAM state to the current schema version.
|
|
120
|
+
* Handles common migration scenarios: adding missing fields, renaming
|
|
121
|
+
* deprecated keys, and setting default values.
|
|
122
|
+
* @param {object} oldState
|
|
123
|
+
* @returns {object} migrated state
|
|
124
|
+
*/
|
|
125
|
+
export function migrateWamState(oldState) {
|
|
126
|
+
if (!oldState || typeof oldState !== 'object') {
|
|
127
|
+
return createWamState('migrated-empty');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const migrated = { ...oldState };
|
|
131
|
+
|
|
132
|
+
if (!('_schemaVersion' in migrated)) {
|
|
133
|
+
migrated._schemaVersion = 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Migration from v0 → v1
|
|
137
|
+
if (migrated._schemaVersion < 1) {
|
|
138
|
+
if (!migrated.createdAt) migrated.createdAt = new Date().toISOString();
|
|
139
|
+
if (!migrated.updatedAt) migrated.updatedAt = migrated.createdAt;
|
|
140
|
+
if (!migrated.requirements) migrated.requirements = {};
|
|
141
|
+
if (!migrated.context) migrated.context = {};
|
|
142
|
+
if (!migrated.evidence) migrated.evidence = {};
|
|
143
|
+
if (!migrated.status) migrated.status = 'active';
|
|
144
|
+
migrated._schemaVersion = SCHEMA_VERSION;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return migrated;
|
|
148
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completion Gate
|
|
3
|
+
*
|
|
4
|
+
* Decides whether a task can be marked complete based on:
|
|
5
|
+
* - every requirement being VERIFIED (via requirement-state)
|
|
6
|
+
* - existence of at least one piece of verified evidence per requirement
|
|
7
|
+
* - absence of contradicting evidence that would invalidate completion
|
|
8
|
+
* - absence of inconclusive evidence that leaves the requirement unresolved
|
|
9
|
+
*
|
|
10
|
+
* API:
|
|
11
|
+
* canComplete(taskRoot, taskId) -> {
|
|
12
|
+
* allowed: boolean,
|
|
13
|
+
* reason: string,
|
|
14
|
+
* blockers: Array<{ requirementId, reason }>,
|
|
15
|
+
* summary: { total, complete, incomplete, blocked },
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* The function is read-only; it does not mutate task state. Callers may use
|
|
19
|
+
* the result to either allow completion, emit a structured failure, or block
|
|
20
|
+
* a "done" claim emitted by an agent.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { getTaskState } from "../skills/engine.js";
|
|
26
|
+
import {
|
|
27
|
+
isRequirementComplete,
|
|
28
|
+
summarizeRequirements,
|
|
29
|
+
REQUIREMENT_STATES,
|
|
30
|
+
} from "../state/requirement-state.js";
|
|
31
|
+
import {
|
|
32
|
+
getAllEvidence,
|
|
33
|
+
getEvidenceForRequirement,
|
|
34
|
+
} from "../evidence/evidence-lineage.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read all evidence for a task (regardless of status). Tolerates errors.
|
|
38
|
+
*/
|
|
39
|
+
function readAllEvidence(taskRoot, taskId) {
|
|
40
|
+
try {
|
|
41
|
+
const ev = getAllEvidence(taskId, taskRoot);
|
|
42
|
+
return Array.isArray(ev) ? ev : [];
|
|
43
|
+
} catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Inspect a single requirement and classify why it would block completion.
|
|
50
|
+
* Returns null if the requirement does NOT block.
|
|
51
|
+
*/
|
|
52
|
+
function findBlocker(requirement, allEvidence) {
|
|
53
|
+
const id = requirement.id;
|
|
54
|
+
const evidenceForReq = allEvidence.filter((e) => e && e.requirementId === id);
|
|
55
|
+
|
|
56
|
+
// 1. No evidence at all -> blocked (regardless of status).
|
|
57
|
+
if (evidenceForReq.length === 0) {
|
|
58
|
+
return {
|
|
59
|
+
requirementId: id,
|
|
60
|
+
reason: `no evidence linked (status=${requirement.status || REQUIREMENT_STATES.PENDING})`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 2. Evidence exists but is contradictory or invalidated -> blocked.
|
|
65
|
+
const statuses = evidenceForReq.map((e) => e.status || e.verdict);
|
|
66
|
+
if (statuses.some((s) => s === "contradictory" || s === "CONTRADICTORY" || s === "INVALIDATED" || s === "invalidated")) {
|
|
67
|
+
return {
|
|
68
|
+
requirementId: id,
|
|
69
|
+
reason: `contradictory or invalidated evidence present`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 3. All evidence is inconclusive -> blocked.
|
|
74
|
+
if (statuses.every((s) => s === "inconclusive" || s === "INCONCLUSIVE")) {
|
|
75
|
+
return {
|
|
76
|
+
requirementId: id,
|
|
77
|
+
reason: `only inconclusive evidence; requirement unresolved`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 4. Requirement is not VERIFIED but has evidence.
|
|
82
|
+
if (!isRequirementComplete(requirement)) {
|
|
83
|
+
// Has evidence but no valid piece yet -> blocked.
|
|
84
|
+
if (!statuses.some((s) => s === "valid" || s === "VERIFIED")) {
|
|
85
|
+
return {
|
|
86
|
+
requirementId: id,
|
|
87
|
+
reason: `no valid evidence for requirement`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
requirementId: id,
|
|
92
|
+
reason: `requirement status is ${requirement.status || REQUIREMENT_STATES.PENDING} (not VERIFIED)`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Decide if a task can complete.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} taskRoot absolute path to the project root (where .wam/ lives)
|
|
103
|
+
* @param {string} taskId task identifier
|
|
104
|
+
* @returns {{allowed: boolean, reason: string, blockers: Array, summary: object}}
|
|
105
|
+
*/
|
|
106
|
+
export function canComplete(taskRoot, taskId) {
|
|
107
|
+
if (!taskRoot || !taskId) {
|
|
108
|
+
return {
|
|
109
|
+
allowed: false,
|
|
110
|
+
reason: "taskRoot and taskId are required",
|
|
111
|
+
blockers: [],
|
|
112
|
+
summary: { total: 0, complete: 0, incomplete: 0, blocked: 0 },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const root = path.resolve(taskRoot);
|
|
117
|
+
if (!fs.existsSync(root)) {
|
|
118
|
+
return {
|
|
119
|
+
allowed: false,
|
|
120
|
+
reason: `task root does not exist: ${root}`,
|
|
121
|
+
blockers: [],
|
|
122
|
+
summary: { total: 0, complete: 0, incomplete: 0, blocked: 0 },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let taskState;
|
|
127
|
+
try {
|
|
128
|
+
taskState = getTaskState(taskId, root);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
return {
|
|
131
|
+
allowed: false,
|
|
132
|
+
reason: `failed to load task state: ${err.message}`,
|
|
133
|
+
blockers: [],
|
|
134
|
+
summary: { total: 0, complete: 0, incomplete: 0, blocked: 0 },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const requirements = Array.isArray(taskState.requirements)
|
|
139
|
+
? taskState.requirements
|
|
140
|
+
: [];
|
|
141
|
+
|
|
142
|
+
if (requirements.length === 0) {
|
|
143
|
+
return {
|
|
144
|
+
allowed: false,
|
|
145
|
+
reason: "task has no requirements",
|
|
146
|
+
blockers: [],
|
|
147
|
+
summary: { total: 0, complete: 0, incomplete: 0, blocked: 0 },
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const allEvidence = readAllEvidence(root, taskId);
|
|
152
|
+
|
|
153
|
+
const blockers = [];
|
|
154
|
+
for (const req of requirements) {
|
|
155
|
+
const blocker = findBlocker(req, allEvidence);
|
|
156
|
+
if (blocker) blockers.push(blocker);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const summary = summarizeRequirements(requirements);
|
|
160
|
+
|
|
161
|
+
if (blockers.length === 0) {
|
|
162
|
+
return {
|
|
163
|
+
allowed: true,
|
|
164
|
+
reason: "all requirements verified",
|
|
165
|
+
blockers: [],
|
|
166
|
+
summary: summary.summary,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
allowed: false,
|
|
172
|
+
reason: `${blockers.length} requirement(s) blocking completion`,
|
|
173
|
+
blockers,
|
|
174
|
+
summary: summary.summary,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Convenience helper: returns true iff canComplete(...).allowed
|
|
180
|
+
*/
|
|
181
|
+
export function isCompletionAllowed(taskRoot, taskId) {
|
|
182
|
+
return canComplete(taskRoot, taskId).allowed;
|
|
183
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function createContract(requirements, verification) {
|
|
2
|
+
return { status: "PROPOSED", requirements, verification, unknowns: [], assumptions: [] };
|
|
3
|
+
}
|
|
4
|
+
export function approveContract(contract) {
|
|
5
|
+
return { ...contract, status: "APPROVED" };
|
|
6
|
+
}
|
|
7
|
+
export function rejectContract(contract) {
|
|
8
|
+
return { ...contract, status: "REJECTED" };
|
|
9
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Contradiction Resolution — explicit conflict detection and resolution.
|
|
3
|
+
*
|
|
4
|
+
* Detects when two pieces of context contradict each other and determines
|
|
5
|
+
* which one governs based on authority precedence.
|
|
6
|
+
*
|
|
7
|
+
* Resolution precedence:
|
|
8
|
+
* 1. user_decided (highest authority)
|
|
9
|
+
* 2. explicit current constraint
|
|
10
|
+
* 3. verified current evidence
|
|
11
|
+
* 4. newer valid decision
|
|
12
|
+
* 5. inferred
|
|
13
|
+
* 6. unknown
|
|
14
|
+
*
|
|
15
|
+
* States: RESOLVED | UNRESOLVED | BLOCKED
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {"RESOLVED" | "UNRESOLVED" | "BLOCKED"} ConflictStatus
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {"user_decided" | "constraint" | "evidence" | "decision" | "inferred" | "unknown"} AuthorityType
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} Conflict
|
|
28
|
+
* @property {string} id
|
|
29
|
+
* @property {string} nodeA - First contradicting node
|
|
30
|
+
* @property {string} nodeB - Second contradicting node
|
|
31
|
+
* @property {string} type - "contradiction" | "invalidation"
|
|
32
|
+
* @property {AuthorityType} authorityA - Authority type of node A
|
|
33
|
+
* @property {AuthorityType} authorityB - Authority type of node B
|
|
34
|
+
* @property {ConflictStatus} status
|
|
35
|
+
* @property {string} winner - Node ID that wins (if resolved)
|
|
36
|
+
* @property {string} reason - Why this resolution was chosen
|
|
37
|
+
* @property {boolean} blocking - Whether this blocks execution
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** Authority precedence (lower number = higher authority) */
|
|
41
|
+
const AUTHORITY_PRECEDENCE = {
|
|
42
|
+
user_decided: 1,
|
|
43
|
+
constraint: 2,
|
|
44
|
+
evidence: 3,
|
|
45
|
+
decision: 4,
|
|
46
|
+
inferred: 5,
|
|
47
|
+
unknown: 6,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Conflict types that block execution */
|
|
51
|
+
const BLOCKING_TYPES = [
|
|
52
|
+
"architecture",
|
|
53
|
+
"security",
|
|
54
|
+
"compatibility",
|
|
55
|
+
"acceptance",
|
|
56
|
+
"destructive",
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Determine the authority type of a node based on its content and metadata.
|
|
61
|
+
*
|
|
62
|
+
* @param {Object} node
|
|
63
|
+
* @returns {AuthorityType}
|
|
64
|
+
*/
|
|
65
|
+
export function getAuthorityType(node) {
|
|
66
|
+
if (!node) return "unknown";
|
|
67
|
+
|
|
68
|
+
const content = (node.content || "").toLowerCase();
|
|
69
|
+
const type = node.type || "";
|
|
70
|
+
|
|
71
|
+
// User decided
|
|
72
|
+
if (content.includes("user decided") || content.includes("user-decided")) {
|
|
73
|
+
return "user_decided";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Constraint
|
|
77
|
+
if (
|
|
78
|
+
type === "constraint" ||
|
|
79
|
+
content.includes("must") ||
|
|
80
|
+
content.includes("shall") ||
|
|
81
|
+
content.includes("mandatory") ||
|
|
82
|
+
content.includes("required")
|
|
83
|
+
) {
|
|
84
|
+
return "constraint";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Evidence
|
|
88
|
+
if (
|
|
89
|
+
type === "evidence" ||
|
|
90
|
+
content.includes("evidence") ||
|
|
91
|
+
content.includes("verified") ||
|
|
92
|
+
content.includes("test passed")
|
|
93
|
+
) {
|
|
94
|
+
return "evidence";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Decision
|
|
98
|
+
if (
|
|
99
|
+
type === "decision" ||
|
|
100
|
+
content.includes("decision") ||
|
|
101
|
+
content.includes("decided") ||
|
|
102
|
+
content.includes("chose") ||
|
|
103
|
+
content.includes("selected")
|
|
104
|
+
) {
|
|
105
|
+
return "decision";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Inferred
|
|
109
|
+
if (
|
|
110
|
+
type === "inferred" ||
|
|
111
|
+
content.includes("inferred") ||
|
|
112
|
+
content.includes("assumed") ||
|
|
113
|
+
content.includes("likely")
|
|
114
|
+
) {
|
|
115
|
+
return "inferred";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return "unknown";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Compare two authority types and determine which wins.
|
|
123
|
+
*
|
|
124
|
+
* @param {AuthorityType} a
|
|
125
|
+
* @param {AuthorityType} b
|
|
126
|
+
* @returns {{ winner: AuthorityType, reason: string }}
|
|
127
|
+
*/
|
|
128
|
+
export function compareAuthority(a, b) {
|
|
129
|
+
const precA = AUTHORITY_PRECEDENCE[a] || 6;
|
|
130
|
+
const precB = AUTHORITY_PRECEDENCE[b] || 6;
|
|
131
|
+
|
|
132
|
+
if (precA < precB) {
|
|
133
|
+
return { winner: a, reason: `${a} has higher precedence than ${b}` };
|
|
134
|
+
}
|
|
135
|
+
if (precB < precA) {
|
|
136
|
+
return { winner: b, reason: `${b} has higher precedence than ${a}` };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Same precedence - use timestamp or other tiebreaker
|
|
140
|
+
return { winner: a, reason: "same precedence, first wins" };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create a conflict object from two contradicting nodes.
|
|
145
|
+
*
|
|
146
|
+
* @param {Object} nodeA
|
|
147
|
+
* @param {Object} nodeB
|
|
148
|
+
* @param {string} type - "contradiction" | "invalidation"
|
|
149
|
+
* @returns {Conflict}
|
|
150
|
+
*/
|
|
151
|
+
export function createConflict(nodeA, nodeB, type = "contradiction") {
|
|
152
|
+
const authorityA = getAuthorityType(nodeA);
|
|
153
|
+
const authorityB = getAuthorityType(nodeB);
|
|
154
|
+
|
|
155
|
+
const { winner, reason } = compareAuthority(authorityA, authorityB);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
id: `conflict-${nodeA.id}-${nodeB.id}`,
|
|
159
|
+
nodeA: nodeA.id,
|
|
160
|
+
nodeB: nodeB.id,
|
|
161
|
+
type,
|
|
162
|
+
authorityA,
|
|
163
|
+
authorityB,
|
|
164
|
+
status: "UNRESOLVED",
|
|
165
|
+
winner: null,
|
|
166
|
+
reason: "",
|
|
167
|
+
blocking: false,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Resolve a conflict based on authority precedence.
|
|
173
|
+
*
|
|
174
|
+
* @param {Conflict} conflict
|
|
175
|
+
* @param {Object} graph - Context graph for node lookup
|
|
176
|
+
* @returns {Conflict} Resolved conflict
|
|
177
|
+
*/
|
|
178
|
+
export function resolveConflict(conflict, graph) {
|
|
179
|
+
const nodeA = graph.getNode(conflict.nodeA);
|
|
180
|
+
const nodeB = graph.getNode(conflict.nodeB);
|
|
181
|
+
|
|
182
|
+
if (!nodeA || !nodeB) {
|
|
183
|
+
conflict.status = "UNRESOLVED";
|
|
184
|
+
conflict.reason = "One or both nodes not found in graph";
|
|
185
|
+
return conflict;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const authorityA = getAuthorityType(nodeA);
|
|
189
|
+
const authorityB = getAuthorityType(nodeB);
|
|
190
|
+
|
|
191
|
+
const { winner, reason } = compareAuthority(authorityA, authorityB);
|
|
192
|
+
|
|
193
|
+
conflict.authorityA = authorityA;
|
|
194
|
+
conflict.authorityB = authorityB;
|
|
195
|
+
conflict.winner = winner === authorityA ? conflict.nodeA : conflict.nodeB;
|
|
196
|
+
conflict.reason = reason;
|
|
197
|
+
conflict.status = "RESOLVED";
|
|
198
|
+
|
|
199
|
+
return conflict;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Check if a conflict should block execution.
|
|
204
|
+
*
|
|
205
|
+
* @param {Conflict} conflict
|
|
206
|
+
* @param {Object} taskContext - Task context for domain detection
|
|
207
|
+
* @returns {boolean}
|
|
208
|
+
*/
|
|
209
|
+
export function shouldBlock(conflict, taskContext = {}) {
|
|
210
|
+
if (conflict.status === "UNRESOLVED") {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check if the conflict affects critical domains
|
|
215
|
+
const content = (taskContext.content || "").toLowerCase();
|
|
216
|
+
for (const domain of BLOCKING_TYPES) {
|
|
217
|
+
if (content.includes(domain)) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Generate conflict report for agent consumption.
|
|
227
|
+
*
|
|
228
|
+
* @param {Conflict[]} conflicts
|
|
229
|
+
* @returns {string} Formatted conflict report
|
|
230
|
+
*/
|
|
231
|
+
export function generateConflictReport(conflicts) {
|
|
232
|
+
if (conflicts.length === 0) {
|
|
233
|
+
return "";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const lines = ["[wam conflicts]"];
|
|
237
|
+
|
|
238
|
+
for (const conflict of conflicts) {
|
|
239
|
+
const status = conflict.status;
|
|
240
|
+
const winner = conflict.winner ? ` → ${conflict.winner}` : "";
|
|
241
|
+
lines.push(` ${conflict.id}: ${conflict.nodeA} vs ${conflict.nodeB} (${status}${winner})`);
|
|
242
|
+
if (conflict.reason) {
|
|
243
|
+
lines.push(` reason: ${conflict.reason}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const unresolved = conflicts.filter((c) => c.status === "UNRESOLVED");
|
|
248
|
+
if (unresolved.length > 0) {
|
|
249
|
+
lines.push(` ⚠ ${unresolved.length} unresolved conflict(s) — do not choose silently`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return lines.join("\n");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export { AUTHORITY_PRECEDENCE, BLOCKING_TYPES };
|