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,126 @@
|
|
|
1
|
+
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
createHypothesis,
|
|
5
|
+
listHypotheses,
|
|
6
|
+
updateHypothesisStatus,
|
|
7
|
+
archiveHypothesis,
|
|
8
|
+
HYPOTHESIS_STATUS,
|
|
9
|
+
} from "./cognition-store.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Cognitive State facade — delegates to cognition-store.js (single source of truth).
|
|
13
|
+
*
|
|
14
|
+
* Mantiene compatibilidad con la API previa (loadCognitiveState, saveCognitiveState,
|
|
15
|
+
* addActiveHypothesis, rejectHypothesis, recordExperiment, recordObservation,
|
|
16
|
+
* compactCognitiveState) pero toda persistencia se redirige a
|
|
17
|
+
* `.wam/tasks/<taskId>/cognition/*.jsonl` vía cognition-store.
|
|
18
|
+
*
|
|
19
|
+
* Si el caller no provee taskId, se intenta derivar de la convención:
|
|
20
|
+
* taskRoot/.wam/tasks/<taskId>/ → primer taskId encontrado.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const DEFAULT_STATE = {
|
|
24
|
+
activeHypotheses: [],
|
|
25
|
+
rejectedHypotheses: [],
|
|
26
|
+
recentExperiments: [],
|
|
27
|
+
criticalObservations: [],
|
|
28
|
+
lastUpdated: null,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function resolveTaskId(taskRoot, explicitTaskId) {
|
|
32
|
+
if (explicitTaskId) return explicitTaskId;
|
|
33
|
+
// Convención: el primer directorio bajo .wam/tasks/
|
|
34
|
+
const tasksRoot = `${taskRoot}/.wam/tasks`;
|
|
35
|
+
try {
|
|
36
|
+
const entries = fs.readdirSync(tasksRoot, { withFileTypes: true });
|
|
37
|
+
const dir = entries.find((e) => e.isDirectory());
|
|
38
|
+
return dir ? dir.name : "default";
|
|
39
|
+
} catch {
|
|
40
|
+
return "default";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildStateFromStore(taskRoot, taskId) {
|
|
45
|
+
const all = listHypotheses(taskRoot, taskId);
|
|
46
|
+
const active = all.filter((h) =>
|
|
47
|
+
h.status === HYPOTHESIS_STATUS.PROPOSED || h.status === HYPOTHESIS_STATUS.TESTING
|
|
48
|
+
);
|
|
49
|
+
const rejected = all.filter((h) => h.status === HYPOTHESIS_STATUS.REJECTED);
|
|
50
|
+
const archived = all.filter((h) => h.status === HYPOTHESIS_STATUS.ARCHIVED);
|
|
51
|
+
const rejectedHypotheses = [...rejected, ...archived];
|
|
52
|
+
return {
|
|
53
|
+
activeHypotheses: active,
|
|
54
|
+
rejectedHypotheses,
|
|
55
|
+
recentExperiments: [],
|
|
56
|
+
criticalObservations: [],
|
|
57
|
+
lastUpdated: Date.now(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function loadCognitiveState(taskRoot, taskId) {
|
|
62
|
+
const tid = resolveTaskId(taskRoot, taskId);
|
|
63
|
+
return buildStateFromStore(taskRoot, tid);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @deprecated La persistencia ahora vive en cognition-store.js.
|
|
68
|
+
* Esta función queda como no-op para no romper callers que aún la invocan.
|
|
69
|
+
*/
|
|
70
|
+
export function saveCognitiveState(_taskRoot, _state) {
|
|
71
|
+
// no-op: cognition-store es la única fuente de verdad.
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function addActiveHypothesis(taskRoot, hypothesis, taskId) {
|
|
75
|
+
const tid = resolveTaskId(taskRoot, taskId);
|
|
76
|
+
createHypothesis(taskRoot, tid, {
|
|
77
|
+
statement: hypothesis.statement,
|
|
78
|
+
confidence: hypothesis.confidence ?? 0.5,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function rejectHypothesis(taskRoot, hypothesisId, reason, taskId) {
|
|
83
|
+
const tid = resolveTaskId(taskRoot, taskId);
|
|
84
|
+
updateHypothesisStatus(taskRoot, tid, hypothesisId, HYPOTHESIS_STATUS.REJECTED);
|
|
85
|
+
// reason se preserva como entrada archivada para trazabilidad.
|
|
86
|
+
archiveHypothesis(taskRoot, tid, hypothesisId, reason || "rejected");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @deprecated Los experimentos se persisten vía cognition-store.createExperiment().
|
|
91
|
+
* Esta función queda como no-op.
|
|
92
|
+
*/
|
|
93
|
+
export function recordExperiment(_taskRoot, _experiment, _taskId) {
|
|
94
|
+
// no-op
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @deprecated Las observaciones se persisten vía cognition-store.recordObservation().
|
|
99
|
+
* Esta función queda como no-op.
|
|
100
|
+
*/
|
|
101
|
+
export function recordObservation(_taskRoot, _observation, _taskId) {
|
|
102
|
+
// no-op
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Compact cognitive state for context injection.
|
|
107
|
+
* Drops redundant observations, keeps actionable signal.
|
|
108
|
+
*/
|
|
109
|
+
export function compactCognitiveState(state, maxBytes = 1500) {
|
|
110
|
+
const compact = {
|
|
111
|
+
activeHypotheses: state.activeHypotheses.slice(0, 3),
|
|
112
|
+
rejectedHypotheses: state.rejectedHypotheses.slice(-3),
|
|
113
|
+
recentExperiments: state.recentExperiments.slice(-3),
|
|
114
|
+
criticalObservations: state.criticalObservations.slice(-3),
|
|
115
|
+
};
|
|
116
|
+
const json = JSON.stringify(compact);
|
|
117
|
+
if (json.length <= maxBytes) return compact;
|
|
118
|
+
|
|
119
|
+
// Aggressive truncation: keep only counts
|
|
120
|
+
return {
|
|
121
|
+
activeHypotheses: compact.activeHypotheses.slice(0, 1),
|
|
122
|
+
rejectedHypotheses: compact.rejectedHypotheses.length,
|
|
123
|
+
recentExperiments: compact.recentExperiments.length,
|
|
124
|
+
criticalObservations: compact.criticalObservations.length,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hypothesis contradiction tracking.
|
|
3
|
+
* Called when an observation contradicts the active hypothesis.
|
|
4
|
+
* Triggers strategy replanning (INVESTIGATING → new hypothesis).
|
|
5
|
+
*
|
|
6
|
+
* Signature (backward compatible):
|
|
7
|
+
* noteContradiction(hypothesisId, observation, assessment)
|
|
8
|
+
* noteContradiction(taskId, hypothesisId, observation, assessment, root)
|
|
9
|
+
*
|
|
10
|
+
* When a hypothesis is rejected, dependent evidence is marked STALE
|
|
11
|
+
* so the linked requirement returns to PENDING and must be replanned.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { invalidateDependentEvidence, getAllEvidence } from "../evidence/evidence-lineage.js";
|
|
15
|
+
import { persistTaskState, getTaskState } from "../skills/engine.js";
|
|
16
|
+
|
|
17
|
+
function isAssessment(value) {
|
|
18
|
+
return value && typeof value === "object" && Array.isArray(value.comparisons);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Reject a hypothesis after a contradicting observation and stale any
|
|
23
|
+
* evidence that was produced under that hypothesis.
|
|
24
|
+
*
|
|
25
|
+
* @returns {{ type: string, hypothesisId: string, detail: string, staleEvidence: Array, pendingRequirements: string[] }}
|
|
26
|
+
*/
|
|
27
|
+
export function noteContradiction(hypothesisIdOrTaskId, observationOrHypothesisId, assessmentOrObservation, maybeAssessment, maybeRoot) {
|
|
28
|
+
let taskId = null;
|
|
29
|
+
let hypothesisId;
|
|
30
|
+
let observation;
|
|
31
|
+
let assessment;
|
|
32
|
+
let root;
|
|
33
|
+
|
|
34
|
+
if (isAssessment(assessmentOrObservation) && maybeAssessment === undefined) {
|
|
35
|
+
// noteContradiction(hypothesisId, observation, assessment)
|
|
36
|
+
hypothesisId = hypothesisIdOrTaskId;
|
|
37
|
+
observation = observationOrHypothesisId;
|
|
38
|
+
assessment = assessmentOrObservation;
|
|
39
|
+
} else {
|
|
40
|
+
// noteContradiction(taskId, hypothesisId, observation, assessment, root)
|
|
41
|
+
taskId = hypothesisIdOrTaskId;
|
|
42
|
+
hypothesisId = observationOrHypothesisId;
|
|
43
|
+
observation = assessmentOrObservation;
|
|
44
|
+
assessment = maybeAssessment;
|
|
45
|
+
root = maybeRoot;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const comparisons = assessment?.comparisons || [];
|
|
49
|
+
const conflictFields = comparisons
|
|
50
|
+
.filter((c) => c.status === "CONTRADICTED")
|
|
51
|
+
.map((c) => `${c.field}: expected ${JSON.stringify(c.expected)}, got ${JSON.stringify(c.actual)}`);
|
|
52
|
+
|
|
53
|
+
const detail = conflictFields.length > 0
|
|
54
|
+
? `Contradicted fields: ${conflictFields.join("; ")}.`
|
|
55
|
+
: `Observation contradicted hypothesis "${hypothesisId}".`;
|
|
56
|
+
|
|
57
|
+
let staleEvidence = [];
|
|
58
|
+
const pendingRequirements = [];
|
|
59
|
+
|
|
60
|
+
if (taskId) {
|
|
61
|
+
staleEvidence = invalidateDependentEvidence(taskId, hypothesisId, detail, root) || [];
|
|
62
|
+
const requirementIds = [...new Set(staleEvidence.map((e) => e.requirementId).filter(Boolean))];
|
|
63
|
+
if (requirementIds.length > 0) {
|
|
64
|
+
const state = getTaskState(taskId, root);
|
|
65
|
+
if (state?.requirements) {
|
|
66
|
+
for (const req of state.requirements) {
|
|
67
|
+
if (requirementIds.includes(req.id)) {
|
|
68
|
+
req.status = "pending";
|
|
69
|
+
req.replan = true;
|
|
70
|
+
pendingRequirements.push(req.id);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
persistTaskState(taskId, state, root);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
// In-memory form used by unit tests / execution-engine (no persistence).
|
|
78
|
+
staleEvidence = [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
type: "CONTRADICTION",
|
|
83
|
+
hypothesisId,
|
|
84
|
+
assessmentResult: assessment?.result,
|
|
85
|
+
reasoning: assessment?.reasoning,
|
|
86
|
+
detail,
|
|
87
|
+
observed: observation,
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
hypothesisStatus: "rejected",
|
|
90
|
+
staleEvidence,
|
|
91
|
+
pendingRequirements,
|
|
92
|
+
replan: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* After a contradiction, mark every requirement that depended on the
|
|
98
|
+
* rejected hypothesis as PENDING so the planner must replan.
|
|
99
|
+
*/
|
|
100
|
+
export function revertRequirementsToPending(taskId, hypothesisId, root) {
|
|
101
|
+
const evidence = getAllEvidence(taskId, root).filter((e) => e.hypothesisId === hypothesisId);
|
|
102
|
+
const requirementIds = [...new Set(evidence.map((e) => e.requirementId).filter(Boolean))];
|
|
103
|
+
const state = getTaskState(taskId, root);
|
|
104
|
+
if (!state?.requirements) return requirementIds;
|
|
105
|
+
for (const req of state.requirements) {
|
|
106
|
+
if (requirementIds.includes(req.id)) {
|
|
107
|
+
req.status = "pending";
|
|
108
|
+
req.replan = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
persistTaskState(taskId, state, root);
|
|
112
|
+
return requirementIds;
|
|
113
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observation Engine — records raw observations for the hypothesis→experiment loop.
|
|
3
|
+
*
|
|
4
|
+
* Pure extraction of observation persistence from execution-engine.js.
|
|
5
|
+
* Owns: the contract of what an observation IS and how it gets persisted.
|
|
6
|
+
* Does NOT own: assessment, evidence creation, or hypothesis status transitions
|
|
7
|
+
* (those live in evidence-engine.js and state-machine.js).
|
|
8
|
+
*
|
|
9
|
+
* A raw observation captures: what actually happened (actual), anything that
|
|
10
|
+
* diverged from the plan (unexpected), provenance (which experiment), and
|
|
11
|
+
* the assessment result + facts that led to recording it.
|
|
12
|
+
*
|
|
13
|
+
* Source of truth for the observation record shape and JSONL append path.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
recordObservation,
|
|
18
|
+
getExperiment,
|
|
19
|
+
} from "./cognition-store.js";
|
|
20
|
+
import { AssessmentResult } from "../policy/assessment-engine.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build a raw observation payload from inputs (pure, no I/O).
|
|
24
|
+
* @param {object} args
|
|
25
|
+
* @param {string|null} args.experimentId
|
|
26
|
+
* @param {string} args.hypothesisId
|
|
27
|
+
* @param {string} args.result - AssessmentResult enum value
|
|
28
|
+
* @param {string[]} args.facts - List of fact strings (e.g. evidence ids, error messages)
|
|
29
|
+
* @param {*} [args.actual]
|
|
30
|
+
* @param {string[]} [args.unexpected]
|
|
31
|
+
* @param {object} [args.provenance]
|
|
32
|
+
* @returns {object} observation payload
|
|
33
|
+
*/
|
|
34
|
+
export function buildObservationPayload({
|
|
35
|
+
experimentId,
|
|
36
|
+
hypothesisId,
|
|
37
|
+
result,
|
|
38
|
+
facts,
|
|
39
|
+
actual,
|
|
40
|
+
unexpected,
|
|
41
|
+
provenance,
|
|
42
|
+
}) {
|
|
43
|
+
return {
|
|
44
|
+
experimentId: experimentId ?? null,
|
|
45
|
+
hypothesisId,
|
|
46
|
+
result,
|
|
47
|
+
facts: Array.isArray(facts) ? facts : [],
|
|
48
|
+
actual: actual ?? null,
|
|
49
|
+
unexpected: Array.isArray(unexpected) ? unexpected : [],
|
|
50
|
+
provenance: provenance ?? null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the experiment record (if known) for the observation context.
|
|
56
|
+
* Returns null when no experimentId is provided or the experiment cannot be found.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} taskRoot
|
|
59
|
+
* @param {string} taskId
|
|
60
|
+
* @param {object} args
|
|
61
|
+
* @param {string|null} args.experimentId
|
|
62
|
+
* @param {string} args.hypothesisId
|
|
63
|
+
* @returns {object|null}
|
|
64
|
+
*/
|
|
65
|
+
export function resolveExperimentContext(taskRoot, taskId, { experimentId, hypothesisId }) {
|
|
66
|
+
if (!experimentId) return null;
|
|
67
|
+
const stored = getExperiment(taskRoot, taskId, experimentId);
|
|
68
|
+
return stored ?? { id: experimentId, hypothesisId };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Default fact-derivation strategy used by noteFailure / noteSuccess.
|
|
73
|
+
* Pure: given inputs, returns facts list. Centralized here so observation
|
|
74
|
+
* contracts stay consistent across callers.
|
|
75
|
+
*
|
|
76
|
+
* @param {object} args
|
|
77
|
+
* @param {string} args.outcome - "success" | "failure"
|
|
78
|
+
* @param {string} [args.reason]
|
|
79
|
+
* @param {*} [args.result]
|
|
80
|
+
* @param {string[]} [args.facts] - pre-built facts (e.g. [evidenceId]); wins if present
|
|
81
|
+
* @returns {string[]}
|
|
82
|
+
*/
|
|
83
|
+
export function deriveDefaultFacts({ outcome, reason, result, facts }) {
|
|
84
|
+
if (Array.isArray(facts) && facts.length > 0) return facts;
|
|
85
|
+
if (outcome === "failure") return reason ? [reason] : ["failure"];
|
|
86
|
+
if (typeof result === "string") return [result];
|
|
87
|
+
if (result === undefined || result === null) return ["ok"];
|
|
88
|
+
return [JSON.stringify(result)];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Record a raw observation. Wraps the cognition-store append with the
|
|
93
|
+
* observation-engine contract: payload shape, fact derivation, and
|
|
94
|
+
* experiment-context resolution.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} taskRoot
|
|
97
|
+
* @param {string} taskId
|
|
98
|
+
* @param {object} args - see buildObservationPayload + deriveDefaultFacts
|
|
99
|
+
* @returns {object} persisted observation record
|
|
100
|
+
*/
|
|
101
|
+
export function recordRawObservation(taskRoot, taskId, args) {
|
|
102
|
+
const experiment = resolveExperimentContext(taskRoot, taskId, args);
|
|
103
|
+
const facts = deriveDefaultFacts(args);
|
|
104
|
+
const payload = buildObservationPayload({ ...args, facts });
|
|
105
|
+
const persisted = recordObservation(taskRoot, taskId, {
|
|
106
|
+
...payload,
|
|
107
|
+
experimentId: experiment?.id ?? payload.experimentId,
|
|
108
|
+
});
|
|
109
|
+
return { observation: persisted, experiment };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { AssessmentResult };
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observation Provenance — unified observation format with provenance tracking.
|
|
3
|
+
*
|
|
4
|
+
* Unifies observation format across task-runs, cognitive-state, and cognition-store.
|
|
5
|
+
* Adds provenance tracking with source, confidence, and session binding.
|
|
6
|
+
*
|
|
7
|
+
* Provenance types:
|
|
8
|
+
* - user_decided: explicitly decided by user (highest confidence)
|
|
9
|
+
* - observed: directly observed from execution (medium confidence)
|
|
10
|
+
* - inferred: inferred from other observations (lower confidence)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {"user_decided" | "observed" | "inferred"} ProvenanceType
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} Provenance
|
|
22
|
+
* @property {ProvenanceType} source - How this observation was obtained
|
|
23
|
+
* @property {number} confidence - 0-1 confidence level
|
|
24
|
+
* @property {string} sessionId - Session that created this observation
|
|
25
|
+
* @property {string} taskId - Task this observation belongs to
|
|
26
|
+
* @property {string} parentId - Parent observation (for inferred chains)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} Observation
|
|
31
|
+
* @property {string} id
|
|
32
|
+
* @property {string} text
|
|
33
|
+
* @property {number} timestamp
|
|
34
|
+
* @property {Provenance} provenance
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/** Default confidence by provenance type */
|
|
38
|
+
const DEFAULT_CONFIDENCE = {
|
|
39
|
+
user_decided: 1.0,
|
|
40
|
+
observed: 0.8,
|
|
41
|
+
inferred: 0.5,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Valid provenance types */
|
|
45
|
+
const VALID_PROVENANCE = new Set(["user_decided", "observed", "inferred"]);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a new observation with provenance.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} text - Observation text
|
|
51
|
+
* @param {ProvenanceType} source - Provenance type
|
|
52
|
+
* @param {Object} options - Additional options
|
|
53
|
+
* @returns {Observation}
|
|
54
|
+
*/
|
|
55
|
+
export function createObservation(text, source = "observed", options = {}) {
|
|
56
|
+
const provenanceType = VALID_PROVENANCE.has(source) ? source : "observed";
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
id: options.id || `obs-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
60
|
+
text,
|
|
61
|
+
timestamp: options.timestamp || Date.now(),
|
|
62
|
+
provenance: {
|
|
63
|
+
source: provenanceType,
|
|
64
|
+
confidence: options.confidence ?? DEFAULT_CONFIDENCE[provenanceType],
|
|
65
|
+
sessionId: options.sessionId || null,
|
|
66
|
+
taskId: options.taskId || null,
|
|
67
|
+
parentId: options.parentId || null,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate observation provenance.
|
|
74
|
+
*
|
|
75
|
+
* @param {Observation} observation
|
|
76
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
77
|
+
*/
|
|
78
|
+
export function validateObservation(observation) {
|
|
79
|
+
const errors = [];
|
|
80
|
+
|
|
81
|
+
if (!observation.id) {
|
|
82
|
+
errors.push("Missing observation id");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!observation.text || typeof observation.text !== "string") {
|
|
86
|
+
errors.push("Missing or invalid observation text");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (typeof observation.timestamp !== "number") {
|
|
90
|
+
errors.push("Missing or invalid timestamp");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!observation.provenance) {
|
|
94
|
+
errors.push("Missing provenance");
|
|
95
|
+
} else {
|
|
96
|
+
if (!VALID_PROVENANCE.has(observation.provenance.source)) {
|
|
97
|
+
errors.push(`Invalid provenance source: ${observation.provenance.source}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
typeof observation.provenance.confidence !== "number" ||
|
|
102
|
+
observation.provenance.confidence < 0 ||
|
|
103
|
+
observation.provenance.confidence > 1
|
|
104
|
+
) {
|
|
105
|
+
errors.push(`Invalid confidence: ${observation.provenance.confidence}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
valid: errors.length === 0,
|
|
111
|
+
errors,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if an observation can be promoted to a higher level.
|
|
117
|
+
*
|
|
118
|
+
* @param {Observation} observation
|
|
119
|
+
* @param {string} targetLevel - Target level (L1, L2, L3, L4)
|
|
120
|
+
* @returns {{ allowed: boolean, reason: string }}
|
|
121
|
+
*/
|
|
122
|
+
export function canPromoteObservation(observation, targetLevel) {
|
|
123
|
+
if (!observation.provenance) {
|
|
124
|
+
return { allowed: false, reason: "Missing provenance" };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { source, confidence } = observation.provenance;
|
|
128
|
+
|
|
129
|
+
// Inferred observations cannot promote to L1
|
|
130
|
+
if (source === "inferred" && targetLevel === "L1") {
|
|
131
|
+
return {
|
|
132
|
+
allowed: false,
|
|
133
|
+
reason: "Inferred observations cannot promote to L1 (user_decided required)",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Low confidence observations cannot promote to L1
|
|
138
|
+
if (confidence < 0.6 && targetLevel === "L1") {
|
|
139
|
+
return {
|
|
140
|
+
allowed: false,
|
|
141
|
+
reason: `Confidence ${confidence} too low for L1 promotion (requires >= 0.6)`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Observed observations can promote to L2-L4
|
|
146
|
+
if (source === "observed" && confidence >= 0.6) {
|
|
147
|
+
return { allowed: true, reason: "Observed with sufficient confidence" };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// User decided can promote anywhere
|
|
151
|
+
if (source === "user_decided") {
|
|
152
|
+
return { allowed: true, reason: "User decided observations can promote anywhere" };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { allowed: false, reason: "Insufficient provenance for promotion" };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Create a derived observation from an existing one.
|
|
160
|
+
* Preserves the observation chain for auditability.
|
|
161
|
+
*
|
|
162
|
+
* @param {Observation} parent - Parent observation
|
|
163
|
+
* @param {string} text - Derived observation text
|
|
164
|
+
* @param {ProvenanceType} source - How this was derived
|
|
165
|
+
* @returns {Observation}
|
|
166
|
+
*/
|
|
167
|
+
export function deriveObservation(parent, text, source = "inferred") {
|
|
168
|
+
return createObservation(text, source, {
|
|
169
|
+
sessionId: parent.provenance.sessionId,
|
|
170
|
+
taskId: parent.provenance.taskId,
|
|
171
|
+
parentId: parent.id,
|
|
172
|
+
confidence: Math.min(parent.provenance.confidence, DEFAULT_CONFIDENCE[source]),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Load observations from a JSONL file.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} filePath
|
|
180
|
+
* @returns {Observation[]}
|
|
181
|
+
*/
|
|
182
|
+
export function loadObservations(filePath) {
|
|
183
|
+
if (!fs.existsSync(filePath)) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
189
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
190
|
+
const observations = [];
|
|
191
|
+
|
|
192
|
+
for (const line of lines) {
|
|
193
|
+
try {
|
|
194
|
+
const obs = JSON.parse(line);
|
|
195
|
+
if (obs.provenance) {
|
|
196
|
+
observations.push(obs);
|
|
197
|
+
}
|
|
198
|
+
} catch {}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return observations;
|
|
202
|
+
} catch {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Save observations to a JSONL file.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} filePath
|
|
211
|
+
* @param {Observation[]} observations
|
|
212
|
+
*/
|
|
213
|
+
export function saveObservations(filePath, observations) {
|
|
214
|
+
const dir = path.dirname(filePath);
|
|
215
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
216
|
+
|
|
217
|
+
const content = observations.map((obs) => JSON.stringify(obs)).join("\n") + "\n";
|
|
218
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Append an observation to a JSONL file.
|
|
223
|
+
*
|
|
224
|
+
* @param {string} filePath
|
|
225
|
+
* @param {Observation} observation
|
|
226
|
+
*/
|
|
227
|
+
export function appendObservation(filePath, observation) {
|
|
228
|
+
const dir = path.dirname(filePath);
|
|
229
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
230
|
+
|
|
231
|
+
fs.appendFileSync(filePath, JSON.stringify(observation) + "\n", "utf-8");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get provenance summary for a set of observations.
|
|
236
|
+
*
|
|
237
|
+
* @param {Observation[]} observations
|
|
238
|
+
* @returns {Object} Summary statistics
|
|
239
|
+
*/
|
|
240
|
+
export function getProvenanceSummary(observations) {
|
|
241
|
+
const bySource = {
|
|
242
|
+
user_decided: 0,
|
|
243
|
+
observed: 0,
|
|
244
|
+
inferred: 0,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
let totalConfidence = 0;
|
|
248
|
+
|
|
249
|
+
for (const obs of observations) {
|
|
250
|
+
if (obs.provenance) {
|
|
251
|
+
bySource[obs.provenance.source] = (bySource[obs.provenance.source] || 0) + 1;
|
|
252
|
+
totalConfidence += obs.provenance.confidence;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const total = observations.length;
|
|
257
|
+
const avgConfidence = total > 0 ? totalConfidence / total : 0;
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
total,
|
|
261
|
+
bySource,
|
|
262
|
+
avgConfidence,
|
|
263
|
+
highConfidence: observations.filter((o) => o.provenance?.confidence >= 0.8).length,
|
|
264
|
+
lowConfidence: observations.filter((o) => o.provenance?.confidence < 0.5).length,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export { VALID_PROVENANCE, DEFAULT_CONFIDENCE };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { LIFECYCLE_STATES, TERMINAL_STATES } from "./context-lifecycle.js";
|
|
2
|
+
import { CONTEXT_EVENT_TYPES } from "./context-event-ingress.js";
|
|
3
|
+
|
|
4
|
+
export const ACTIVE_LIFECYCLES = new Set(["CREATED", "ACTIVE"]);
|
|
5
|
+
export const SUPPORTING_LIFECYCLES = new Set(["COMPRESSED", "STALE"]);
|
|
6
|
+
export const EXCLUDED_LIFECYCLES = new Set(["INVALIDATED", "ARCHIVED"]);
|
|
7
|
+
|
|
8
|
+
export const PURPOSE_MODES = Object.freeze({
|
|
9
|
+
PLANNING: "PLANNING",
|
|
10
|
+
IMPLEMENTATION: "IMPLEMENTATION",
|
|
11
|
+
INVESTIGATION: "INVESTIGATION",
|
|
12
|
+
DEBUGGING: "DEBUGGING",
|
|
13
|
+
VALIDATION: "VALIDATION",
|
|
14
|
+
REVIEW: "REVIEW",
|
|
15
|
+
RESUME: "RESUME",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export function retrieveActiveContext(items, purpose = PURPOSE_MODES.PLANNING) {
|
|
19
|
+
const isDebugOrResume = [PURPOSE_MODES.DEBUGGING, PURPOSE_MODES.RESUME].includes(purpose);
|
|
20
|
+
const includeInvalidated = isDebugOrResume || purpose === PURPOSE_MODES.REPLAN || purpose === PURPOSE_MODES.AUDIT;
|
|
21
|
+
|
|
22
|
+
return items.filter((item) => {
|
|
23
|
+
const lc = item.lifecycle;
|
|
24
|
+
if (ACTIVE_LIFECYCLES.has(lc)) return true;
|
|
25
|
+
if (SUPPORTING_LIFECYCLES.has(lc)) return true;
|
|
26
|
+
if (EXCLUDED_LIFECYCLES.has(lc)) return includeInvalidated;
|
|
27
|
+
return true;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isContextSufficient(items, purpose = PURPOSE_MODES.PLANNING) {
|
|
32
|
+
const active = retrieveActiveContext(items, purpose);
|
|
33
|
+
const activeCount = active.length;
|
|
34
|
+
const hasCriticalMissing = active.some((item) => {
|
|
35
|
+
return item.unresolvedCriticalUnknowns?.length > 0;
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
sufficient: activeCount > 0 && !hasCriticalMissing,
|
|
39
|
+
missing: hasCriticalMissing ? active.flatMap((item) => item.unresolvedCriticalUnknowns || []) : [],
|
|
40
|
+
activeCount,
|
|
41
|
+
mandatoryIncluded: active.some((item) => item.mandatoryIncluded === true),
|
|
42
|
+
unresolvedCriticalUnknowns: hasCriticalMissing ? active.flatMap((item) => item.unresolvedCriticalUnknowns || []) : [],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function invalidateContextItems(items, event) {
|
|
47
|
+
const { reason, invalidatedIds, scope } = event.payload || {};
|
|
48
|
+
const affected = [];
|
|
49
|
+
|
|
50
|
+
for (const item of items) {
|
|
51
|
+
if (invalidatedIds?.includes(item.id)) {
|
|
52
|
+
affected.push({ ...item, lifecycle: LIFECYCLE_STATES.INVALIDATED, invalidatedAt: Date.now(), invalidationReason: reason });
|
|
53
|
+
} else if (EXCLUDED_LIFECYCLES.has(item.lifecycle) && !invalidatedIds?.includes(item.id)) {
|
|
54
|
+
continue;
|
|
55
|
+
} else {
|
|
56
|
+
affected.push(item);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return affected;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function markItemsStale(items, triggerEvent) {
|
|
64
|
+
return items.map((item) => {
|
|
65
|
+
if (EXCLUDED_LIFECYCLES.has(item.lifecycle)) return item;
|
|
66
|
+
return { ...item, lifecycle: LIFECYCLE_STATES.STALE, staleReason: triggerEvent?.payload?.reason || "context-stale" };
|
|
67
|
+
});
|
|
68
|
+
}
|