@jstn-sdk/ma 0.1.13 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +0 -0
- package/.codex/agents/Architect.toml +1 -0
- package/.codex/agents/Auditor.toml +1 -0
- package/.codex/agents/Builder.toml +1 -0
- package/.codex/agents/Flow.toml +1 -0
- package/.codex/agents/Sage.toml +1 -0
- package/.codex/agents/Vibe.toml +1 -0
- package/.codex/hooks.json +12 -2
- package/.codex/prompts/enforcement.md +0 -0
- package/.codex/prompts/onboarding.md +6 -1
- package/.codex/prompts/release-rules.md +0 -0
- package/.codex/prompts/skill-contract.md +0 -0
- package/COVERAGE.md +9 -9
- package/DEMO.md +15 -6
- package/LICENSE +0 -0
- package/README.md +127 -784
- package/bin/ma.js +591 -105
- package/data/clone-data.ledger.json +0 -0
- package/data/clone-data.proof.json +0 -0
- package/data/clone-data.rvf +0 -0
- package/docs/README.md +8 -2
- package/docs/assets/banner.png +0 -0
- package/docs/autonomous-tasks.md +46 -0
- package/docs/codex-integration.md +16 -0
- package/docs/getting-started.md +32 -8
- package/docs/installed-sdk.md +0 -0
- package/docs/mcp-setup.md +30 -1
- package/docs/qa/{release-issue-gates-0.1.13.json → release-issue-gates-0.14.1.json} +77 -77
- package/docs/qa/release-readiness-0.1.5.md +0 -0
- package/docs/qa/{release-readiness-0.1.13.md → release-readiness-0.14.1.md} +12 -12
- package/docs/quality.md +24 -0
- package/docs/reference/native-engineering-patterns.md +0 -0
- package/docs/reference/native-security-playbooks.md +0 -0
- package/docs/reference/native-source-selection.md +0 -0
- package/docs/reference/native-style-and-deslop.md +0 -0
- package/docs/release-spec.md +27 -14
- package/docs/skills.md +1 -1
- package/docs/vendor-plugin-publishing.md +49 -0
- package/index.js +73 -0
- package/mcp/collections.json +0 -0
- package/mcp/fallback.json +0 -0
- package/mcp/local/code-intel.js +185 -41
- package/mcp/local/context.js +180 -0
- package/mcp/local/memory.js +17 -6
- package/mcp/local/playbooks.js +0 -0
- package/mcp/local/quality.js +39 -0
- package/mcp/local/state.js +7 -3
- package/mcp/local/team-run.js +8 -3
- package/mcp/local/trace.js +0 -0
- package/mcp/local-capabilities.json +9 -0
- package/mcp/native-playbooks.json +0 -0
- package/mcp/servers.json +0 -0
- package/package.json +37 -6
- package/plugins/meta-architect/.app.json +1 -1
- package/plugins/meta-architect/.claude-plugin/plugin.json +12 -0
- package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
- package/plugins/meta-architect/.mcp.json +1 -1
- package/plugins/meta-architect/README.md +14 -2
- package/plugins/meta-architect/obsidian/main.js +137 -4
- package/plugins/meta-architect/obsidian/manifest.json +1 -1
- package/plugins/meta-architect/obsidian/styles.css +0 -0
- package/plugins/meta-architect/skills/align/SKILL.md +0 -0
- package/plugins/meta-architect/skills/align/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/align/references/shared-language.md +0 -0
- package/plugins/meta-architect/skills/arch/SKILL.md +0 -0
- package/plugins/meta-architect/skills/arch/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/build/SKILL.md +0 -0
- package/plugins/meta-architect/skills/build/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/cleanup/SKILL.md +0 -0
- package/plugins/meta-architect/skills/cleanup/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/cleanup/references/style-and-deslop.md +0 -0
- package/plugins/meta-architect/skills/diagnose/SKILL.md +0 -0
- package/plugins/meta-architect/skills/diagnose/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/flow/SKILL.md +0 -0
- package/plugins/meta-architect/skills/flow/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/maestro/SKILL.md +17 -1
- package/plugins/meta-architect/skills/maestro/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/maestro/references/core-release-rules.md +2 -2
- package/plugins/meta-architect/skills/maestro/references/native-ingest-map.md +0 -0
- package/plugins/meta-architect/skills/sage/SKILL.md +0 -0
- package/plugins/meta-architect/skills/sage/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/sage/references/source-selection.md +0 -0
- package/plugins/meta-architect/skills/tdd/SKILL.md +0 -0
- package/plugins/meta-architect/skills/tdd/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/vet/SKILL.md +0 -0
- package/plugins/meta-architect/skills/vet/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/vet/references/security-playbooks.md +0 -0
- package/plugins/meta-architect/skills/vibe/SKILL.md +0 -0
- package/plugins/meta-architect/skills/vibe/agents/openai.yaml +0 -0
- package/schemas/autonomous-task-queue.schema.json +58 -0
- package/schemas/handoff-packet.schema.json +36 -0
- package/schemas/setup-receipt.schema.json +28 -0
- package/schemas/skill-frontmatter.schema.json +20 -0
- package/schemas/support-bundle.schema.json +37 -0
- package/schemas/task-contract.schema.json +31 -0
- package/scripts/active-autonomy-hook.mjs +141 -11
- package/scripts/biome-staged.js +0 -0
- package/scripts/build-linux-packages.mjs +7 -3
- package/scripts/cleanup-test-fixtures.sh +55 -0
- package/scripts/context-hydration-hook.mjs +89 -0
- package/scripts/demo-smoke.js +285 -0
- package/scripts/doctor.js +10 -8
- package/scripts/install.sh +33 -4
- package/scripts/install.sh.sha256 +1 -0
- package/scripts/linux-package-lib.mjs +0 -0
- package/scripts/linux-package-smoke.mjs +2 -2
- package/scripts/package-size-check.mjs +26 -0
- package/scripts/plugin-build.js +314 -0
- package/scripts/plugin-publish.js +77 -0
- package/scripts/plugin-sync.js +83 -17
- package/scripts/prepack.js +9 -0
- package/scripts/ralph/prompt.md +0 -0
- package/scripts/release-metadata.js +0 -0
- package/scripts/release-sync.js +83 -7
- package/scripts/release-verify.js +131 -7
- package/scripts/setup-npmrc.js +48 -23
- package/scripts/skills-install.js +9 -2
- package/scripts/skills-manifest.js +2 -21
- package/scripts/skills-pack.js +2 -2
- package/scripts/skills-validate.js +5 -55
- package/skills/align/SKILL.md +0 -0
- package/skills/align/agents/openai.yaml +0 -0
- package/skills/align/references/shared-language.md +0 -0
- package/skills/arch/SKILL.md +0 -0
- package/skills/arch/agents/openai.yaml +0 -0
- package/skills/build/SKILL.md +0 -0
- package/skills/build/agents/openai.yaml +0 -0
- package/skills/cleanup/SKILL.md +0 -0
- package/skills/cleanup/agents/openai.yaml +0 -0
- package/skills/cleanup/references/style-and-deslop.md +0 -0
- package/skills/diagnose/SKILL.md +0 -0
- package/skills/diagnose/agents/openai.yaml +0 -0
- package/skills/flow/SKILL.md +0 -0
- package/skills/flow/agents/openai.yaml +0 -0
- package/skills/index.json +0 -0
- package/skills/maestro/SKILL.md +17 -1
- package/skills/maestro/agents/openai.yaml +0 -0
- package/skills/maestro/references/core-release-rules.md +2 -2
- package/skills/maestro/references/native-ingest-map.md +0 -0
- package/skills/sage/SKILL.md +0 -0
- package/skills/sage/agents/openai.yaml +0 -0
- package/skills/sage/references/source-selection.md +0 -0
- package/skills/tdd/SKILL.md +0 -0
- package/skills/tdd/agents/openai.yaml +0 -0
- package/skills/vet/SKILL.md +0 -0
- package/skills/vet/agents/openai.yaml +0 -0
- package/skills/vet/references/security-playbooks.md +0 -0
- package/skills/vibe/SKILL.md +0 -0
- package/skills/vibe/agents/openai.yaml +0 -0
- package/sprint/00-idea.md +0 -0
- package/sprint/01-architecture.md +0 -0
- package/sprint/02-oss-evidence.md +0 -0
- package/sprint/03-logic.md +0 -0
- package/sprint/04-security.md +0 -0
- package/sprint/05-dx-ux.md +0 -0
- package/sprint/06-build-plan.md +0 -0
- package/sprint/07-release.md +2 -2
- package/src/agents.js +281 -0
- package/src/bootstrap.js +272 -65
- package/src/build-gate.js +0 -0
- package/src/codex-app-server.js +291 -0
- package/src/decision-log.js +0 -0
- package/src/fs-utils.js +175 -4
- package/src/launcher.js +26 -23
- package/src/mcp-config.js +9 -1
- package/src/mcp-live-client.js +141 -8
- package/src/paths.js +6 -1
- package/src/policy.js +2 -2
- package/src/prelaunch.js +223 -0
- package/src/process-utils.js +62 -0
- package/src/quality/ai-quality-orchestrator.js +328 -0
- package/src/release-issue-gates.js +62 -0
- package/src/release-operations.js +72 -0
- package/src/release-state.js +0 -0
- package/src/runtime/active-autonomy-core.js +0 -0
- package/src/runtime/agent-compat.js +31 -0
- package/src/runtime/alignment-sentinel.js +0 -0
- package/src/runtime/architect-review.js +249 -11
- package/src/runtime/autonomous-tasks.js +585 -0
- package/src/runtime/build-readiness.js +0 -0
- package/src/runtime/code-graph-rehearse.js +0 -0
- package/src/runtime/codeburn-core.js +118 -0
- package/src/runtime/context-authority.js +40 -0
- package/src/runtime/context-economy-core.js +40 -0
- package/src/runtime/continuity-graph.js +201 -0
- package/src/runtime/continuity-notes.js +71 -26
- package/src/runtime/core-source-ingest.js +2 -2
- package/src/runtime/detached-provider.js +90 -13
- package/src/runtime/doctor-report.js +18 -0
- package/src/runtime/environment-awareness-core.js +0 -0
- package/src/runtime/exposure-catalog.js +0 -0
- package/src/runtime/graphify-core.js +167 -0
- package/src/runtime/guidance-stack.js +9 -2
- package/src/runtime/handoff-packets.js +92 -0
- package/src/runtime/headroom-core.js +104 -0
- package/src/runtime/helper-orchestration-core.js +0 -0
- package/src/runtime/hook-profiles.js +20 -0
- package/src/runtime/learning-loop-core.js +110 -4
- package/src/runtime/live-agent-verification.js +122 -0
- package/src/runtime/maestro-events.js +0 -0
- package/src/runtime/maestro-manager.js +0 -0
- package/src/runtime/maestro-output.js +58 -0
- package/src/runtime/maestro-state.js +0 -0
- package/src/runtime/managed-markdown.js +29 -0
- package/src/runtime/mcp-authority.js +147 -0
- package/src/runtime/mcp-policy.js +0 -0
- package/src/runtime/obsidian-integration-core.js +255 -17
- package/src/runtime/obsidian-plugin-bridge.js +344 -38
- package/src/runtime/orchestrator.js +0 -0
- package/src/runtime/pi-maestro-core.js +89 -0
- package/src/runtime/preferences.js +79 -0
- package/src/runtime/project-context.js +636 -0
- package/src/runtime/prompt-strategy-core.js +0 -0
- package/src/runtime/quorum-review.js +0 -0
- package/src/runtime/ralph-execution-core.js +0 -0
- package/src/runtime/redaction-gateway.js +218 -19
- package/src/runtime/runtime-state.js +94 -2
- package/src/runtime/schema-migrations.js +162 -0
- package/src/runtime/semantic-recording-core.js +0 -0
- package/src/runtime/signal-hooks.js +13 -0
- package/src/runtime/skills-registry-export.js +96 -8
- package/src/runtime/startup-path.js +0 -0
- package/src/runtime/task-contracts.js +83 -0
- package/src/runtime/universal-plugin-broker-core.js +373 -50
- package/src/runtime/workspace-intelligence-runtime.js +0 -0
- package/src/runtime/workspace-virtualizer.js +0 -0
- package/src/runtime/workspaces.js +0 -0
- package/src/runtime-artifacts.js +0 -0
- package/src/setup-lifecycle.js +359 -0
- package/src/skill-frontmatter.js +78 -0
- package/src/skill-installer.js +226 -71
- package/src/skills.js +309 -31
- package/src/state-sync.js +0 -0
- package/src/test-fixtures.js +543 -0
- package/src/tui/grid.js +67 -0
- package/src/tui/status-grid.js +23 -0
- package/support-bundle.json +114 -0
- package/templates/AGENTS.md +6 -2
- package/templates/catalog-manifest.json +0 -0
- package/templates/model-instructions/core.md +0 -0
- package/templates/model-instructions/release.md +0 -0
- package/templates/model-instructions/security.md +0 -0
- package/templates/quality/ai-quality-rules.yml +37 -0
- package/docs/assets/image/Screenshot(1).png +0 -0
- package/docs/assets/image/Screenshot(2).png +0 -0
- package/docs/assets/image/Screenshot(3).png +0 -0
- package/docs/assets/image/Screenshot(4).png +0 -0
- package/docs/assets/image/Screenshot(5).png +0 -0
- package/docs/assets/image/Screenshot(6).png +0 -0
- package/docs/assets/image/Screenshot(7).png +0 -0
- package/docs/assets/image/Screenshot(8).png +0 -0
- package/docs/assets/image/Screenshot(9).png +0 -0
- package/docs/assets/meta-architect-logo.png +0 -0
- package/docs/assets/meta-architect-logo.svg +0 -8
- package/docs/onboarding.md +0 -81
- package/docs/prompt-guidance-contract.md +0 -17
- package/docs/prompt-guidance-fragments/active-autonomy-core.md +0 -7
- package/docs/skills-publishing.md +0 -255
- package/scripts/postinstall.js +0 -23
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const contextAuthorityOrder = [
|
|
2
|
+
"source_truth",
|
|
3
|
+
"verified_evidence",
|
|
4
|
+
"hook_evidence",
|
|
5
|
+
"generated_context",
|
|
6
|
+
"vault_note",
|
|
7
|
+
"learning_memory",
|
|
8
|
+
"external_reference",
|
|
9
|
+
"stale",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export function createFreshness({
|
|
13
|
+
sourceHash = null,
|
|
14
|
+
sourceFiles = [],
|
|
15
|
+
checkedAt = new Date().toISOString(),
|
|
16
|
+
status = "fresh",
|
|
17
|
+
} = {}) {
|
|
18
|
+
return {
|
|
19
|
+
status,
|
|
20
|
+
stale: status === "stale",
|
|
21
|
+
checkedAt,
|
|
22
|
+
sourceHash,
|
|
23
|
+
sourceFiles: [...sourceFiles],
|
|
24
|
+
changedFiles: [...sourceFiles],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function resolveContextConflict(candidates = []) {
|
|
29
|
+
return (
|
|
30
|
+
[...candidates]
|
|
31
|
+
.filter((candidate) => candidate && typeof candidate === "object")
|
|
32
|
+
.sort((left, right) => {
|
|
33
|
+
const authority =
|
|
34
|
+
contextAuthorityOrder.indexOf(left.authority) -
|
|
35
|
+
contextAuthorityOrder.indexOf(right.authority);
|
|
36
|
+
if (authority !== 0) return authority;
|
|
37
|
+
return Number(Boolean(left.freshness?.stale)) - Number(Boolean(right.freshness?.stale));
|
|
38
|
+
})[0] ?? null
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -184,6 +184,46 @@ export function createMcpDescriptorEconomy({ name, description, inputSchema = {}
|
|
|
184
184
|
};
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
export function createBudgetedContext({ items = [], budgetChars = 12000, topic } = {}) {
|
|
188
|
+
const candidates = items
|
|
189
|
+
.filter((item) => item && typeof item.id === "string" && typeof item.text === "string")
|
|
190
|
+
.map((item, index) => ({ ...item, tier: item.tier ?? index }));
|
|
191
|
+
const topical = topic
|
|
192
|
+
? candidates.filter((item) =>
|
|
193
|
+
`${item.topic ?? ""} ${item.id}`.toLowerCase().includes(topic.toLowerCase()),
|
|
194
|
+
)
|
|
195
|
+
: candidates;
|
|
196
|
+
const pool = topical.length > 0 ? topical : candidates;
|
|
197
|
+
let remaining = Math.max(0, budgetChars);
|
|
198
|
+
const loaded = [];
|
|
199
|
+
const skipped = candidates
|
|
200
|
+
.filter((item) => !pool.includes(item))
|
|
201
|
+
.map((item) => ({ id: item.id, reason: "topic_mismatch" }));
|
|
202
|
+
const context = {};
|
|
203
|
+
|
|
204
|
+
for (const item of [...pool].sort((left, right) => left.tier - right.tier)) {
|
|
205
|
+
const view = createContextEconomyView({ text: item.text, level: "budgeted_context" });
|
|
206
|
+
if (view.output_chars <= remaining) {
|
|
207
|
+
context[item.id] = view.output;
|
|
208
|
+
remaining -= view.output_chars;
|
|
209
|
+
loaded.push(item.id);
|
|
210
|
+
} else {
|
|
211
|
+
skipped.push({ id: item.id, reason: "budget_exhausted" });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
record_type: "budgeted_context",
|
|
217
|
+
budget_chars: budgetChars,
|
|
218
|
+
used_chars: budgetChars - remaining,
|
|
219
|
+
topic: topic ?? null,
|
|
220
|
+
loaded,
|
|
221
|
+
skipped,
|
|
222
|
+
fallback_expanded: Boolean(topic && topical.length === 0),
|
|
223
|
+
context,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
187
227
|
export function validateContextEconomyCore(value) {
|
|
188
228
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
189
229
|
throw new Error("Context Economy core must be an object");
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { ensureDir, readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
|
|
3
|
+
import { getRuntimeSubsystemPath } from "../paths.js";
|
|
4
|
+
|
|
5
|
+
export const continuityGraphSchemaVersion = "0.1.0";
|
|
6
|
+
|
|
7
|
+
export function getContinuityGraphPath() {
|
|
8
|
+
return getRuntimeSubsystemPath("memory", "graph.json");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createDefaultContinuityGraph() {
|
|
12
|
+
return {
|
|
13
|
+
schemaVersion: continuityGraphSchemaVersion,
|
|
14
|
+
record_type: "continuity_knowledge_graph",
|
|
15
|
+
humanReadableMirror: ".ma/memory/notes.md",
|
|
16
|
+
nodes: [],
|
|
17
|
+
edges: [],
|
|
18
|
+
lastUpdatedAt: null,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function validateContinuityGraph(value) {
|
|
23
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
24
|
+
throw new Error("continuity graph must be an object");
|
|
25
|
+
}
|
|
26
|
+
if (value.schemaVersion !== continuityGraphSchemaVersion) {
|
|
27
|
+
throw new Error(`Unsupported continuity graph schemaVersion: ${value.schemaVersion}`);
|
|
28
|
+
}
|
|
29
|
+
if (value.record_type !== "continuity_knowledge_graph") {
|
|
30
|
+
throw new Error("continuity graph has an invalid record_type");
|
|
31
|
+
}
|
|
32
|
+
if (!Array.isArray(value.nodes) || !Array.isArray(value.edges)) {
|
|
33
|
+
throw new Error("continuity graph requires nodes and edges arrays");
|
|
34
|
+
}
|
|
35
|
+
for (const node of value.nodes) {
|
|
36
|
+
if (
|
|
37
|
+
!node ||
|
|
38
|
+
typeof node !== "object" ||
|
|
39
|
+
typeof node.id !== "string" ||
|
|
40
|
+
typeof node.type !== "string" ||
|
|
41
|
+
typeof node.label !== "string" ||
|
|
42
|
+
typeof node.scope !== "string" ||
|
|
43
|
+
!Number.isInteger(node.mentions) ||
|
|
44
|
+
node.mentions < 0
|
|
45
|
+
) {
|
|
46
|
+
throw new Error("continuity graph contains an invalid node");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const nodeIds = new Set(value.nodes.map((node) => node.id));
|
|
50
|
+
for (const edge of value.edges) {
|
|
51
|
+
if (
|
|
52
|
+
!edge ||
|
|
53
|
+
typeof edge !== "object" ||
|
|
54
|
+
typeof edge.id !== "string" ||
|
|
55
|
+
typeof edge.source !== "string" ||
|
|
56
|
+
typeof edge.target !== "string" ||
|
|
57
|
+
typeof edge.relation !== "string" ||
|
|
58
|
+
!nodeIds.has(edge.source) ||
|
|
59
|
+
!nodeIds.has(edge.target)
|
|
60
|
+
) {
|
|
61
|
+
throw new Error("continuity graph contains an invalid edge");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function seedContinuityGraphArtifacts() {
|
|
68
|
+
await ensureDir(getRuntimeSubsystemPath("memory"));
|
|
69
|
+
await writeFileIfMissing(
|
|
70
|
+
getContinuityGraphPath(),
|
|
71
|
+
`${JSON.stringify(createDefaultContinuityGraph(), null, 2)}\n`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function loadContinuityGraph() {
|
|
76
|
+
return validateContinuityGraph(await readJson(getContinuityGraphPath()));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function stableId(prefix, value) {
|
|
80
|
+
return `${prefix}:${createHash("sha256").update(value).digest("hex").slice(0, 16)}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function deriveEntities(content, explicit = []) {
|
|
84
|
+
const values = [
|
|
85
|
+
...explicit,
|
|
86
|
+
...(content.match(/`([^`]+)`|#([a-z][\w-]*)|\b[A-Z][A-Za-z0-9_-]{2,}\b/g) ?? []),
|
|
87
|
+
]
|
|
88
|
+
.map((value) => value.replaceAll("`", "").replace(/^#/, "").trim().toLowerCase())
|
|
89
|
+
.filter((value) => value.length >= 3 && value.length <= 80);
|
|
90
|
+
return [...new Set(values)].slice(0, 32);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function mergeContinuityGraph(
|
|
94
|
+
graph,
|
|
95
|
+
{ content, scope, timestamp, entities = [], relationships = [] },
|
|
96
|
+
) {
|
|
97
|
+
const next = validateContinuityGraph(graph);
|
|
98
|
+
const noteId = stableId("note", `${timestamp}\0${scope}\0${content}`);
|
|
99
|
+
const entityNames = deriveEntities(content, entities);
|
|
100
|
+
const nodes = [...next.nodes];
|
|
101
|
+
const edges = [...next.edges];
|
|
102
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
103
|
+
const edgeKeys = new Set(edges.map((edge) => `${edge.source}\0${edge.target}\0${edge.relation}`));
|
|
104
|
+
const addNode = (node) => {
|
|
105
|
+
const existing = nodeById.get(node.id);
|
|
106
|
+
if (existing) {
|
|
107
|
+
existing.updatedAt = timestamp;
|
|
108
|
+
existing.mentions = (existing.mentions ?? 0) + (node.mentions ?? 0);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
nodeById.set(node.id, node);
|
|
112
|
+
nodes.push(node);
|
|
113
|
+
};
|
|
114
|
+
const addEdge = (source, target, relation) => {
|
|
115
|
+
const key = `${source}\0${target}\0${relation}`;
|
|
116
|
+
if (source === target || edgeKeys.has(key)) return;
|
|
117
|
+
edgeKeys.add(key);
|
|
118
|
+
edges.push({ id: stableId("edge", key), source, target, relation, updatedAt: timestamp });
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
addNode({
|
|
122
|
+
id: noteId,
|
|
123
|
+
type: "session",
|
|
124
|
+
label: `${scope} note`,
|
|
125
|
+
scope,
|
|
126
|
+
mentions: 1,
|
|
127
|
+
createdAt: timestamp,
|
|
128
|
+
updatedAt: timestamp,
|
|
129
|
+
});
|
|
130
|
+
const entityIds = entityNames.map((name) => {
|
|
131
|
+
const id = stableId("entity", name);
|
|
132
|
+
addNode({
|
|
133
|
+
id,
|
|
134
|
+
type: "entity",
|
|
135
|
+
label: name,
|
|
136
|
+
scope,
|
|
137
|
+
mentions: 1,
|
|
138
|
+
createdAt: timestamp,
|
|
139
|
+
updatedAt: timestamp,
|
|
140
|
+
});
|
|
141
|
+
addEdge(noteId, id, "mentions");
|
|
142
|
+
return id;
|
|
143
|
+
});
|
|
144
|
+
for (let index = 0; index < entityIds.length; index += 1) {
|
|
145
|
+
for (let other = index + 1; other < entityIds.length; other += 1)
|
|
146
|
+
addEdge(entityIds[index], entityIds[other], "co_occurs");
|
|
147
|
+
}
|
|
148
|
+
for (const relationship of relationships) {
|
|
149
|
+
if (!relationship || typeof relationship !== "object") continue;
|
|
150
|
+
const from = stableId("entity", String(relationship.from ?? "").toLowerCase());
|
|
151
|
+
const to = stableId("entity", String(relationship.to ?? "").toLowerCase());
|
|
152
|
+
if (nodeById.has(from) && nodeById.has(to) && relationship.relation)
|
|
153
|
+
addEdge(from, to, String(relationship.relation));
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
...next,
|
|
157
|
+
nodes: nodes.slice(-1000),
|
|
158
|
+
edges: edges.slice(-4000),
|
|
159
|
+
lastUpdatedAt: timestamp,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function queryContinuityGraph(graph, { nodeId, label, depth = 2, limit = 100 } = {}) {
|
|
164
|
+
const value = validateContinuityGraph(graph);
|
|
165
|
+
const normalizedLabel = label?.trim().toLowerCase();
|
|
166
|
+
const boundedLimit = Math.max(1, Math.min(Number(limit) || 100, 1000));
|
|
167
|
+
const starts = value.nodes.filter(
|
|
168
|
+
(node) =>
|
|
169
|
+
node.id === nodeId || (normalizedLabel && node.label.toLowerCase() === normalizedLabel),
|
|
170
|
+
);
|
|
171
|
+
if (starts.length === 0) return { nodes: [], edges: [], depth: 0 };
|
|
172
|
+
const included = new Set(starts.map((node) => node.id));
|
|
173
|
+
let frontier = [...included];
|
|
174
|
+
const maxDepth = Math.max(0, Math.min(Number(depth) || 0, 5));
|
|
175
|
+
for (let level = 0; level < maxDepth; level += 1) {
|
|
176
|
+
const next = [];
|
|
177
|
+
for (const edge of value.edges) {
|
|
178
|
+
if (frontier.includes(edge.source) && !included.has(edge.target)) next.push(edge.target);
|
|
179
|
+
if (frontier.includes(edge.target) && !included.has(edge.source)) next.push(edge.source);
|
|
180
|
+
}
|
|
181
|
+
frontier = [...new Set(next)].slice(0, boundedLimit);
|
|
182
|
+
frontier.forEach((id) => {
|
|
183
|
+
included.add(id);
|
|
184
|
+
});
|
|
185
|
+
if (frontier.length === 0) break;
|
|
186
|
+
}
|
|
187
|
+
const nodes = value.nodes.filter((node) => included.has(node.id)).slice(0, boundedLimit);
|
|
188
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
189
|
+
return {
|
|
190
|
+
nodes,
|
|
191
|
+
edges: value.edges.filter((edge) => ids.has(edge.source) && ids.has(edge.target)),
|
|
192
|
+
depth: maxDepth,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function mergeContinuityGraphEntry(entry) {
|
|
197
|
+
const graph = await loadContinuityGraph();
|
|
198
|
+
const next = mergeContinuityGraph(graph, entry);
|
|
199
|
+
await writeJson(getContinuityGraphPath(), next);
|
|
200
|
+
return next;
|
|
201
|
+
}
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
ensureDir,
|
|
5
|
+
readJson,
|
|
6
|
+
writeFileIfMissing,
|
|
7
|
+
writeJson,
|
|
8
|
+
writeTextAtomically,
|
|
9
|
+
} from "../fs-utils.js";
|
|
3
10
|
import { getRuntimeSubsystemPath } from "../paths.js";
|
|
11
|
+
import { mergeContinuityGraphEntry, seedContinuityGraphArtifacts } from "./continuity-graph.js";
|
|
4
12
|
import { guardLeaderMutation } from "./runtime-state.js";
|
|
5
13
|
|
|
14
|
+
export const knowledgeScopes = ["personal/local", "project-shared", "team/process", "session-only"];
|
|
15
|
+
let continuityWriteQueue = Promise.resolve();
|
|
16
|
+
const scopeFiles = {
|
|
17
|
+
"personal/local": "personal.md",
|
|
18
|
+
"project-shared": "notes.md",
|
|
19
|
+
"team/process": "team.md",
|
|
20
|
+
"session-only": "session.md",
|
|
21
|
+
};
|
|
22
|
+
|
|
6
23
|
export function getContinuityRoot() {
|
|
7
24
|
return getRuntimeSubsystemPath("memory");
|
|
8
25
|
}
|
|
@@ -21,6 +38,13 @@ export async function seedContinuityArtifacts() {
|
|
|
21
38
|
getContinuityNotesPath(),
|
|
22
39
|
"# Continuity Notes\n\nNo continuity notes captured yet.\n",
|
|
23
40
|
);
|
|
41
|
+
await seedContinuityGraphArtifacts();
|
|
42
|
+
for (const file of ["personal.md", "team.md", "session.md"]) {
|
|
43
|
+
await writeFileIfMissing(
|
|
44
|
+
path.join(getContinuityRoot(), file),
|
|
45
|
+
`# ${file.replace(".md", "")} knowledge\n\nNo notes captured yet.\n`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
24
48
|
await writeFileIfMissing(
|
|
25
49
|
getContinuityIndexPath(),
|
|
26
50
|
`${JSON.stringify(
|
|
@@ -28,6 +52,7 @@ export async function seedContinuityArtifacts() {
|
|
|
28
52
|
schemaVersion: "0.1.0",
|
|
29
53
|
sessionCount: 0,
|
|
30
54
|
lastUpdatedAt: null,
|
|
55
|
+
knowledgeScopes,
|
|
31
56
|
},
|
|
32
57
|
null,
|
|
33
58
|
2,
|
|
@@ -36,7 +61,17 @@ export async function seedContinuityArtifacts() {
|
|
|
36
61
|
}
|
|
37
62
|
|
|
38
63
|
export async function loadContinuityNotes() {
|
|
39
|
-
return
|
|
64
|
+
return loadScopedKnowledge({ scopes: ["project-shared", "team/process"] });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function loadScopedKnowledge({ scopes = ["project-shared", "team/process"] } = {}) {
|
|
68
|
+
const selected = scopes.filter((scope) => knowledgeScopes.includes(scope));
|
|
69
|
+
const notes = await Promise.all(
|
|
70
|
+
selected.map((scope) =>
|
|
71
|
+
fs.readFile(path.join(getContinuityRoot(), scopeFiles[scope]), "utf8").catch(() => ""),
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
return notes.filter(Boolean).join("\n\n");
|
|
40
75
|
}
|
|
41
76
|
|
|
42
77
|
export async function loadContinuityIndex() {
|
|
@@ -47,33 +82,43 @@ export async function storeContinuityNote(content, options = {}) {
|
|
|
47
82
|
const guard = await guardLeaderMutation({
|
|
48
83
|
actor: options.actor,
|
|
49
84
|
kind: "memory-note-write",
|
|
50
|
-
payload: { content },
|
|
85
|
+
payload: { content, scope: options.scope ?? "session-only" },
|
|
51
86
|
});
|
|
52
87
|
if (!guard.allowed) {
|
|
53
88
|
return { proposed: true, proposalPath: guard.proposalPath };
|
|
54
89
|
}
|
|
55
90
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
throw new Error("memory.store_note requires non-empty content");
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
const operation = continuityWriteQueue.then(async () => {
|
|
92
|
+
const note = content.trim();
|
|
93
|
+
if (!note) throw new Error("memory.store_note requires non-empty content");
|
|
94
|
+
const scope = options.scope ?? "project-shared";
|
|
95
|
+
if (!knowledgeScopes.includes(scope)) throw new Error(`Unsupported knowledge scope: ${scope}`);
|
|
96
|
+
const [existingNotes, existingIndex] = await Promise.all([
|
|
97
|
+
fs.readFile(path.join(getContinuityRoot(), scopeFiles[scope]), "utf8").catch(() => ""),
|
|
98
|
+
loadContinuityIndex(),
|
|
99
|
+
]);
|
|
100
|
+
const timestamp = new Date().toISOString();
|
|
101
|
+
const nextNotes = `${existingNotes.trimEnd()}\n\n## ${timestamp}\n\n${note}\n`;
|
|
102
|
+
const nextIndex = {
|
|
103
|
+
...existingIndex,
|
|
104
|
+
sessionCount: existingIndex.sessionCount + 1,
|
|
105
|
+
lastUpdatedAt: timestamp,
|
|
106
|
+
scopeCounts: {
|
|
107
|
+
...(existingIndex.scopeCounts ?? {}),
|
|
108
|
+
[scope]: (existingIndex.scopeCounts?.[scope] ?? 0) + 1,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
await writeTextAtomically(path.join(getContinuityRoot(), scopeFiles[scope]), nextNotes);
|
|
112
|
+
await writeJson(getContinuityIndexPath(), nextIndex);
|
|
113
|
+
await mergeContinuityGraphEntry({
|
|
114
|
+
content: note,
|
|
115
|
+
scope,
|
|
116
|
+
timestamp,
|
|
117
|
+
entities: options.entities,
|
|
118
|
+
relationships: options.relationships,
|
|
119
|
+
});
|
|
120
|
+
return { proposed: false, proposalPath: null, index: nextIndex };
|
|
121
|
+
});
|
|
122
|
+
continuityWriteQueue = operation.catch(() => {});
|
|
123
|
+
return operation;
|
|
79
124
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { createHash } from "node:crypto";
|
|
3
2
|
import fs from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { ensureDir, readJson, writeFileIfMissing, writeJson } from "../fs-utils.js";
|
|
6
5
|
import { getRepoRoot, getRuntimeSubsystemPath, getRuntimeWritePath } from "../paths.js";
|
|
6
|
+
import { safeSpawn } from "../process-utils.js";
|
|
7
7
|
|
|
8
8
|
export const coreSourceIngestSchemaVersion = "0.1.0";
|
|
9
9
|
|
|
@@ -153,7 +153,7 @@ export async function loadCoreSourceIngest() {
|
|
|
153
153
|
export async function ingestCoreSources({
|
|
154
154
|
definitions = coreSourceDefinitions,
|
|
155
155
|
refresh = false,
|
|
156
|
-
spawnImpl =
|
|
156
|
+
spawnImpl = safeSpawn,
|
|
157
157
|
} = {}) {
|
|
158
158
|
await ensureDir(getCoreSourcesRootPath());
|
|
159
159
|
const sources = [];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import fs from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { ensureDir } from "../fs-utils.js";
|
|
5
4
|
import { getRuntimeSubsystemPath } from "../paths.js";
|
|
5
|
+
import { assertSafeExecutable, safeSpawnSync } from "../process-utils.js";
|
|
6
6
|
|
|
7
7
|
export function getRuntimeLogsRoot() {
|
|
8
8
|
return getRuntimeSubsystemPath("logs");
|
|
@@ -11,7 +11,10 @@ export function getRuntimeLogsRoot() {
|
|
|
11
11
|
export function resolveDetachedProvider() {
|
|
12
12
|
const preferred = process.env.MA_DETACHED_PROVIDER ?? "";
|
|
13
13
|
if (preferred === "tmux" || process.env.MA_TMUX_PROVIDER === "1") {
|
|
14
|
-
const probe =
|
|
14
|
+
const probe = safeSpawnSync("tmux", ["ls"], {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
env: buildDetachedEnv(),
|
|
17
|
+
});
|
|
15
18
|
if (!probe.error) {
|
|
16
19
|
return "tmux";
|
|
17
20
|
}
|
|
@@ -21,16 +24,18 @@ export function resolveDetachedProvider() {
|
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export async function launchDetachedTrack({ trackId, title, command, args = [] }) {
|
|
27
|
+
assertSafeExecutable(command, "Detached provider command");
|
|
28
|
+
if (!Array.isArray(args) || args.some((arg) => typeof arg !== "string" || arg.includes("\0"))) {
|
|
29
|
+
throw new Error("Detached provider arguments must be strings without NUL bytes");
|
|
30
|
+
}
|
|
24
31
|
const provider = resolveDetachedProvider();
|
|
25
32
|
await ensureDir(getRuntimeLogsRoot());
|
|
26
|
-
const
|
|
33
|
+
const safeTrackId = sanitizeTrackId(trackId);
|
|
34
|
+
const logPath = path.join(getRuntimeLogsRoot(), `${safeTrackId}.log`);
|
|
35
|
+
const metadata = `title_present=${Boolean(title)}\ncommand=${commandBasename(command)}\narg_count=${args.length}\n`;
|
|
27
36
|
|
|
28
37
|
if (provider !== "tmux") {
|
|
29
|
-
await fs.writeFile(
|
|
30
|
-
logPath,
|
|
31
|
-
`provider=none\ntitle=${title}\ncommand=${[command, ...args].join(" ")}\n`,
|
|
32
|
-
"utf8",
|
|
33
|
-
);
|
|
38
|
+
await fs.writeFile(logPath, `provider=none\n${metadata}`, "utf8");
|
|
34
39
|
return {
|
|
35
40
|
provider: "none",
|
|
36
41
|
executionPane: null,
|
|
@@ -40,12 +45,16 @@ export async function launchDetachedTrack({ trackId, title, command, args = [] }
|
|
|
40
45
|
};
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
const sessionName = `ma_${
|
|
44
|
-
|
|
45
|
-
const
|
|
48
|
+
const sessionName = `ma_${safeTrackId}`;
|
|
49
|
+
await fs.writeFile(logPath, `provider=tmux\n${metadata}`, "utf8");
|
|
50
|
+
const tmuxCommand = [command, ...args].map(quoteForShell).join(" ");
|
|
51
|
+
const started = safeSpawnSync(
|
|
46
52
|
"tmux",
|
|
47
|
-
["new-session", "-d", "-s", sessionName, "
|
|
48
|
-
{
|
|
53
|
+
["new-session", "-d", "-s", sessionName, "--", tmuxCommand],
|
|
54
|
+
{
|
|
55
|
+
encoding: "utf8",
|
|
56
|
+
env: buildDetachedEnv(),
|
|
57
|
+
},
|
|
49
58
|
);
|
|
50
59
|
|
|
51
60
|
if (started.status !== 0) {
|
|
@@ -64,6 +73,31 @@ export async function launchDetachedTrack({ trackId, title, command, args = [] }
|
|
|
64
73
|
};
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
const piped = safeSpawnSync(
|
|
77
|
+
"tmux",
|
|
78
|
+
["pipe-pane", "-t", sessionName, "-o", `cat >> ${quoteForShell(logPath)}`],
|
|
79
|
+
{ encoding: "utf8", env: buildDetachedEnv() },
|
|
80
|
+
);
|
|
81
|
+
if (piped.status !== 0) {
|
|
82
|
+
safeSpawnSync("tmux", ["kill-session", "-t", sessionName], {
|
|
83
|
+
encoding: "utf8",
|
|
84
|
+
env: buildDetachedEnv(),
|
|
85
|
+
});
|
|
86
|
+
await fs.writeFile(
|
|
87
|
+
logPath,
|
|
88
|
+
`provider=tmux\nstatus=failed\nstderr=${piped.stderr ?? ""}\n`,
|
|
89
|
+
"utf8",
|
|
90
|
+
);
|
|
91
|
+
return {
|
|
92
|
+
provider: "tmux",
|
|
93
|
+
executionPane: sessionName,
|
|
94
|
+
pid: null,
|
|
95
|
+
logPath,
|
|
96
|
+
started: false,
|
|
97
|
+
error: piped.stderr?.trim() || "tmux log pipe failed",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
67
101
|
return {
|
|
68
102
|
provider: "tmux",
|
|
69
103
|
executionPane: sessionName,
|
|
@@ -72,3 +106,46 @@ export async function launchDetachedTrack({ trackId, title, command, args = [] }
|
|
|
72
106
|
started: true,
|
|
73
107
|
};
|
|
74
108
|
}
|
|
109
|
+
|
|
110
|
+
const detachedEnvKeys = [
|
|
111
|
+
"PATH",
|
|
112
|
+
"HOME",
|
|
113
|
+
"USERPROFILE",
|
|
114
|
+
"TMPDIR",
|
|
115
|
+
"TMP",
|
|
116
|
+
"TEMP",
|
|
117
|
+
"LANG",
|
|
118
|
+
"LC_ALL",
|
|
119
|
+
"SYSTEMROOT",
|
|
120
|
+
"COMSPEC",
|
|
121
|
+
"MA_ROOT",
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
function buildDetachedEnv() {
|
|
125
|
+
return Object.fromEntries(
|
|
126
|
+
detachedEnvKeys
|
|
127
|
+
.filter((key) => process.env[key] !== undefined)
|
|
128
|
+
.map((key) => [key, process.env[key]]),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function sanitizeTrackId(trackId) {
|
|
133
|
+
const safe = String(trackId ?? "")
|
|
134
|
+
.replace(/[^a-zA-Z0-9_-]/g, "_")
|
|
135
|
+
.replace(/^_+|_+$/g, "");
|
|
136
|
+
return safe || "track";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function commandBasename(command) {
|
|
140
|
+
return (
|
|
141
|
+
String(command ?? "")
|
|
142
|
+
.split(/[\\/]/)
|
|
143
|
+
.pop() || "unknown"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function quoteForShell(value) {
|
|
148
|
+
// The provider command never enters a shell; this quote is only for tmux's
|
|
149
|
+
// fixed `cat >> path` pipe command.
|
|
150
|
+
return `'${String(value).replaceAll("'", `'\\''`)}'`;
|
|
151
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function summarizeDoctorStatuses(statuses) {
|
|
2
|
+
if (statuses.some((status) => status.kind === "BLOCKED")) return "BLOCKED";
|
|
3
|
+
if (statuses.some((status) => status.kind === "WARN")) return "READY_WITH_WARNINGS";
|
|
4
|
+
return "READY";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function printDoctorStatuses(title, statuses, write = console.log) {
|
|
8
|
+
write(title);
|
|
9
|
+
write("=".repeat(title.length));
|
|
10
|
+
for (const status of statuses) {
|
|
11
|
+
const label = status.kind.padEnd(7, " ");
|
|
12
|
+
write(`${label} ${status.label}${status.detail ? `: ${status.detail}` : ""}`);
|
|
13
|
+
}
|
|
14
|
+
const result = summarizeDoctorStatuses(statuses);
|
|
15
|
+
write("");
|
|
16
|
+
write(`Result: ${result}`);
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
File without changes
|
|
File without changes
|