intentdna 1.6.5 → 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +26 -15
- package/dist/cli/commands/compile.js +2 -47
- package/dist/cli/commands/context.d.ts +8 -0
- package/dist/cli/commands/context.js +63 -0
- package/dist/cli/commands/feedback.d.ts +3 -2
- package/dist/cli/commands/feedback.js +16 -5
- package/dist/cli/commands/init.js +11 -63
- package/dist/cli/commands/run.js +5 -4
- package/dist/cli/commands/show.js +2 -38
- package/dist/cli/commands/sync.d.ts +9 -6
- package/dist/cli/commands/sync.js +191 -186
- package/dist/cli/commands/templates.d.ts +10 -1
- package/dist/cli/commands/templates.js +50 -1
- package/dist/cli/commands/validate.js +15 -9
- package/dist/cli/commands/verify.d.ts +32 -0
- package/dist/cli/commands/verify.js +270 -31
- package/dist/cli/index.js +78 -11
- package/dist/compiler/activate.js +11 -6
- package/dist/compiler/cascade.d.ts +5 -1
- package/dist/compiler/cascade.js +74 -1
- package/dist/compiler/compile.js +38 -0
- package/dist/compiler/diagnostics.d.ts +17 -0
- package/dist/compiler/diagnostics.js +30 -0
- package/dist/compiler/index.d.ts +7 -0
- package/dist/compiler/index.js +13 -13
- package/dist/compiler/input-resolver.d.ts +32 -0
- package/dist/compiler/input-resolver.js +281 -0
- package/dist/compiler/provenance.d.ts +8 -0
- package/dist/compiler/provenance.js +127 -0
- package/dist/governance/index.d.ts +4 -3
- package/dist/governance/index.js +3 -4
- package/dist/governance/runtime-decision-event.d.ts +85 -0
- package/dist/governance/runtime-decision-event.js +231 -0
- package/dist/governance/types.d.ts +3 -3
- package/dist/governance/types.js +3 -3
- package/dist/hooks/cli.d.ts +10 -1
- package/dist/hooks/cli.js +199 -35
- package/dist/hooks/state.d.ts +5 -10
- package/dist/hooks/state.js +170 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/tools-compile.js +18 -49
- package/dist/mcp/tools-context.d.ts +2 -0
- package/dist/mcp/tools-context.js +85 -0
- package/dist/mcp/tools-enforce.d.ts +2 -2
- package/dist/mcp/tools-enforce.js +19 -49
- package/dist/mcp/tools-observability.js +26 -2
- package/dist/mcp/tools-state.d.ts +1 -1
- package/dist/mcp/tools-state.js +17 -7
- package/dist/report/kernel-report.d.ts +27 -0
- package/dist/report/kernel-report.js +60 -19
- package/dist/report/kernel-signals.d.ts +5 -2
- package/dist/report/kernel-signals.js +87 -2
- package/dist/report/report-package.d.ts +64 -0
- package/dist/report/report-package.js +90 -0
- package/dist/runtime/agent-md.d.ts +1 -0
- package/dist/runtime/agent-md.js +21 -3
- package/dist/runtime/claude-sdk.d.ts +9 -4
- package/dist/runtime/claude-sdk.js +9 -0
- package/dist/runtime/context-sources.d.ts +14 -0
- package/dist/runtime/context-sources.js +60 -0
- package/dist/runtime/plugin-adapter.d.ts +5 -1
- package/dist/runtime/plugin-adapter.js +2 -0
- package/dist/runtime/skill-adapter.d.ts +32 -4
- package/dist/runtime/skill-adapter.js +184 -9
- package/dist/runtime/workflow-runner.d.ts +1 -1
- package/dist/runtime/workflow-runner.js +1 -1
- package/dist/schema/types.d.ts +84 -0
- package/dist/schema/validate.js +156 -2
- package/dist/schema/validators/controllers.js +16 -0
- package/dist/signals/index.d.ts +10 -0
- package/dist/signals/index.js +90 -5
- package/dist/templates/catalog.d.ts +19 -0
- package/dist/templates/catalog.js +57 -0
- package/dist/templates/flutter-rewrite.dna.yaml +2 -2
- package/package.json +1 -1
- package/spec/README.md +1 -1
- package/spec/foundation-hardening.md +2 -1
- package/spec/schema-spec.md +78 -1
package/dist/hooks/state.d.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* All write operations use atomic temp+rename pattern.
|
|
11
11
|
*/
|
|
12
12
|
import type { ArtifactFact, ArtifactKey, ArtifactManifest, CompletedArtifactEntry, HandoffArtifact, HandoffType, VerifierKind, VerifierSeverity, VerifierWhen } from "../schema/types.js";
|
|
13
|
+
import { type RuntimeDecisionEvent } from "../governance/index.js";
|
|
13
14
|
/** Workflow state written by runtime and read by hooks */
|
|
14
15
|
export interface DNAWorkflowState {
|
|
15
16
|
active: boolean;
|
|
@@ -136,6 +137,7 @@ export declare function writeSessionReads(projectDir: string, state: SessionRead
|
|
|
136
137
|
*/
|
|
137
138
|
export declare function appendSessionRead(projectDir: string, filePath: string, sessionId?: string): Promise<void>;
|
|
138
139
|
export interface VerifierResultEntry {
|
|
140
|
+
result_id?: string;
|
|
139
141
|
verifier_id: string;
|
|
140
142
|
when: VerifierWhen;
|
|
141
143
|
severity: VerifierSeverity;
|
|
@@ -151,6 +153,7 @@ export interface VerifierResultEntry {
|
|
|
151
153
|
message?: string;
|
|
152
154
|
timestamp: string;
|
|
153
155
|
}
|
|
156
|
+
export declare function verifierResultId(result: Omit<VerifierResultEntry, "result_id"> | VerifierResultEntry): string;
|
|
154
157
|
export declare function readVerifierResults(projectDir: string, sessionId?: string): Promise<VerifierResultEntry[]>;
|
|
155
158
|
export declare function writeVerifierResults(projectDir: string, results: VerifierResultEntry[], sessionId?: string): Promise<void>;
|
|
156
159
|
export declare function appendVerifierResult(projectDir: string, result: VerifierResultEntry, sessionId?: string): Promise<void>;
|
|
@@ -178,16 +181,8 @@ export interface TraceEntry {
|
|
|
178
181
|
* Fail-open: never throws.
|
|
179
182
|
*/
|
|
180
183
|
export declare function appendTrace(projectDir: string, entry: TraceEntry, sessionId?: string): Promise<void>;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
*
|
|
184
|
-
* G1 session isolation:
|
|
185
|
-
* - With sessionId: reads from `.dna/state/sessions/{id}/trace.jsonl`
|
|
186
|
-
* (falls back to legacy global files with sessionId in name for backward compat)
|
|
187
|
-
* - Without sessionId: reads from `.dna/state/trace/trace-{date}.jsonl` (global merged view)
|
|
188
|
-
*
|
|
189
|
-
* Returns parsed entries sorted by timestamp.
|
|
190
|
-
*/
|
|
184
|
+
export declare function appendRuntimeDecisionEvent(projectDir: string, event: RuntimeDecisionEvent, sessionId?: string): Promise<void>;
|
|
185
|
+
export declare function readRuntimeDecisionEvents(projectDir: string, days?: number, sessionId?: string): Promise<RuntimeDecisionEvent[]>;
|
|
191
186
|
export declare function readTraces(projectDir: string, days?: number, sessionId?: string): Promise<TraceEntry[]>;
|
|
192
187
|
/**
|
|
193
188
|
* List active session directories under `.dna/state/sessions/`.
|
package/dist/hooks/state.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { readFile, writeFile, rename, mkdir, appendFile, unlink, readdir, stat } from "node:fs/promises";
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
14
|
import { join, dirname } from "node:path";
|
|
15
|
+
import { CASCADE_LAYER_NAMES, RUNTIME_DECISION_EVENT_SCHEMA_VERSION } from "../governance/index.js";
|
|
15
16
|
// ── Constants ──────────────────────────────────────────────
|
|
16
17
|
const WORKFLOW_FILE = "workflow.json";
|
|
17
18
|
const DEFAULT_STALENESS_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
@@ -330,13 +331,34 @@ export async function appendSessionRead(projectDir, filePath, sessionId) {
|
|
|
330
331
|
}
|
|
331
332
|
}
|
|
332
333
|
const VERIFIER_RESULTS_FILE = "workflow/verifier-results.json";
|
|
334
|
+
export function verifierResultId(result) {
|
|
335
|
+
return "vr_" + createHash("sha256")
|
|
336
|
+
.update(JSON.stringify({
|
|
337
|
+
verifier_id: result.verifier_id,
|
|
338
|
+
when: result.when,
|
|
339
|
+
severity: result.severity,
|
|
340
|
+
kind: result.kind,
|
|
341
|
+
workflow: result.workflow ?? "",
|
|
342
|
+
step_id: result.step_id ?? "",
|
|
343
|
+
status: result.status,
|
|
344
|
+
target: result.target ?? "",
|
|
345
|
+
evidence: result.evidence ?? "",
|
|
346
|
+
artifact: result.artifact ?? "",
|
|
347
|
+
timestamp: result.timestamp,
|
|
348
|
+
}))
|
|
349
|
+
.digest("hex")
|
|
350
|
+
.slice(0, 16);
|
|
351
|
+
}
|
|
352
|
+
function normalizeVerifierResult(result) {
|
|
353
|
+
return result.result_id ? result : { ...result, result_id: verifierResultId(result) };
|
|
354
|
+
}
|
|
333
355
|
async function readVerifierResultsFile(projectDir, sessionId) {
|
|
334
356
|
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
335
357
|
const filePath = join(stateDir, VERIFIER_RESULTS_FILE);
|
|
336
358
|
try {
|
|
337
359
|
const raw = await readFile(filePath, "utf-8");
|
|
338
360
|
const parsed = JSON.parse(raw);
|
|
339
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
361
|
+
return Array.isArray(parsed) ? parsed.map(normalizeVerifierResult) : [];
|
|
340
362
|
}
|
|
341
363
|
catch {
|
|
342
364
|
return [];
|
|
@@ -381,10 +403,11 @@ export async function writeVerifierResults(projectDir, results, sessionId) {
|
|
|
381
403
|
}
|
|
382
404
|
export async function appendVerifierResult(projectDir, result, sessionId) {
|
|
383
405
|
const results = await readVerifierResultsFile(projectDir, sessionId);
|
|
384
|
-
results.push(result);
|
|
406
|
+
results.push(normalizeVerifierResult(result));
|
|
385
407
|
await writeVerifierResults(projectDir, results, sessionId);
|
|
386
408
|
}
|
|
387
409
|
const TRACE_DIR = "trace";
|
|
410
|
+
const RUNTIME_DECISION_DIR = "runtime-decisions";
|
|
388
411
|
const MAX_TRACE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
389
412
|
const TRACE_RETENTION_DAYS = 7;
|
|
390
413
|
/**
|
|
@@ -446,6 +469,151 @@ export async function appendTrace(projectDir, entry, sessionId) {
|
|
|
446
469
|
*
|
|
447
470
|
* Returns parsed entries sorted by timestamp.
|
|
448
471
|
*/
|
|
472
|
+
function datedJsonlFileName(prefix, date) {
|
|
473
|
+
return `${prefix}-${date}.jsonl`;
|
|
474
|
+
}
|
|
475
|
+
function decisionFileName(date) {
|
|
476
|
+
return datedJsonlFileName("runtime-decision", date);
|
|
477
|
+
}
|
|
478
|
+
export async function appendRuntimeDecisionEvent(projectDir, event, sessionId) {
|
|
479
|
+
try {
|
|
480
|
+
const line = JSON.stringify(event) + "\n";
|
|
481
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
482
|
+
const globalDir = join(projectDir, ".dna", "state", RUNTIME_DECISION_DIR);
|
|
483
|
+
await mkdir(globalDir, { recursive: true });
|
|
484
|
+
await appendFile(join(globalDir, decisionFileName(date)), line, "utf-8");
|
|
485
|
+
if (sessionId) {
|
|
486
|
+
const sessionDir = resolveStateDir(projectDir, sessionId);
|
|
487
|
+
await mkdir(sessionDir, { recursive: true });
|
|
488
|
+
await appendFile(join(sessionDir, "runtime-decisions.jsonl"), line, "utf-8");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
// Fail-open: evidence write failure never affects hook execution
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function normalizeRuntimeDecisionEvent(value) {
|
|
496
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
497
|
+
return null;
|
|
498
|
+
const record = value;
|
|
499
|
+
const hasDecision = record.decision === "allow" || record.decision === "warn" || record.decision === "block" || record.decision === "escalate" || record.decision === "validate";
|
|
500
|
+
const decision = hasDecision ? record.decision : "unknown";
|
|
501
|
+
const schemaVersion = record.schema_version === RUNTIME_DECISION_EVENT_SCHEMA_VERSION ? RUNTIME_DECISION_EVENT_SCHEMA_VERSION : "unknown";
|
|
502
|
+
const hasTimestamp = typeof record.timestamp === "string" && record.timestamp.trim().length > 0;
|
|
503
|
+
const cascadeLayers = Object.fromEntries(CASCADE_LAYER_NAMES.map((layer) => {
|
|
504
|
+
const entry = typeof record.cascade_layers === "object" && record.cascade_layers !== null && !Array.isArray(record.cascade_layers)
|
|
505
|
+
? record.cascade_layers[layer]
|
|
506
|
+
: undefined;
|
|
507
|
+
const layerRecord = typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry : {};
|
|
508
|
+
return [layer, {
|
|
509
|
+
layer,
|
|
510
|
+
source_id: typeof layerRecord.source_id === "string" ? layerRecord.source_id : "unknown",
|
|
511
|
+
source_ref: typeof layerRecord.source_ref === "string" ? layerRecord.source_ref : "unknown",
|
|
512
|
+
fingerprint: typeof layerRecord.fingerprint === "string" ? layerRecord.fingerprint : "unknown",
|
|
513
|
+
status: layerRecord.status === "active" || layerRecord.status === "unsupported" ? layerRecord.status : "unknown",
|
|
514
|
+
}];
|
|
515
|
+
}));
|
|
516
|
+
const winningConstraint = typeof record.winning_constraint === "object" && record.winning_constraint !== null && !Array.isArray(record.winning_constraint)
|
|
517
|
+
? record.winning_constraint
|
|
518
|
+
: {};
|
|
519
|
+
const harnessRuntimeContext = typeof record.harness_runtime_context === "object" && record.harness_runtime_context !== null && !Array.isArray(record.harness_runtime_context)
|
|
520
|
+
? record.harness_runtime_context
|
|
521
|
+
: {};
|
|
522
|
+
const evidenceRefs = Array.isArray(record.evidence_refs)
|
|
523
|
+
? record.evidence_refs.filter((ref) => typeof ref === "object" && ref !== null && !Array.isArray(ref) && typeof ref.type === "string" && typeof ref.ref === "string")
|
|
524
|
+
: [];
|
|
525
|
+
return {
|
|
526
|
+
schema_version: schemaVersion,
|
|
527
|
+
event_id: typeof record.event_id === "string" && record.event_id ? record.event_id : "unknown",
|
|
528
|
+
timestamp: hasTimestamp ? record.timestamp : "unknown",
|
|
529
|
+
org_id: typeof record.org_id === "string" ? record.org_id : "unknown",
|
|
530
|
+
team_id: typeof record.team_id === "string" ? record.team_id : "unknown",
|
|
531
|
+
user_id: typeof record.user_id === "string" ? record.user_id : "unknown",
|
|
532
|
+
project_id: typeof record.project_id === "string" ? record.project_id : "unknown",
|
|
533
|
+
agent_id: typeof record.agent_id === "string" ? record.agent_id : "unknown",
|
|
534
|
+
agent_role: typeof record.agent_role === "string" ? record.agent_role : "unknown",
|
|
535
|
+
agent_type: typeof record.agent_type === "string" ? record.agent_type : "unknown",
|
|
536
|
+
session_agent: typeof record.session_agent === "string" ? record.session_agent : "unknown",
|
|
537
|
+
harness_adapter: typeof record.harness_adapter === "string" ? record.harness_adapter : "unknown",
|
|
538
|
+
runtime: typeof record.runtime === "string" ? record.runtime : "unknown",
|
|
539
|
+
policy_bundle_id: typeof record.policy_bundle_id === "string" ? record.policy_bundle_id : "unknown",
|
|
540
|
+
policy_bundle_version: typeof record.policy_bundle_version === "string" ? record.policy_bundle_version : "unknown",
|
|
541
|
+
cascade_layers: cascadeLayers,
|
|
542
|
+
winning_constraint: {
|
|
543
|
+
constraint_id: typeof winningConstraint.constraint_id === "string" ? winningConstraint.constraint_id : "unknown",
|
|
544
|
+
source_layer: typeof winningConstraint.source_layer === "string" && CASCADE_LAYER_NAMES.some((layer) => layer === winningConstraint.source_layer) ? winningConstraint.source_layer : "unknown",
|
|
545
|
+
source_dna_id: typeof winningConstraint.source_dna_id === "string" ? winningConstraint.source_dna_id : "unknown",
|
|
546
|
+
gene_id: typeof winningConstraint.gene_id === "string" ? winningConstraint.gene_id : "unknown",
|
|
547
|
+
codon_type: typeof winningConstraint.codon_type === "string" ? winningConstraint.codon_type : "unknown",
|
|
548
|
+
action: decision,
|
|
549
|
+
provenance_reason: typeof winningConstraint.provenance_reason === "string" ? winningConstraint.provenance_reason : "unknown",
|
|
550
|
+
},
|
|
551
|
+
compiled_ir_hash: typeof record.compiled_ir_hash === "string" ? record.compiled_ir_hash : "unknown",
|
|
552
|
+
decision: decision,
|
|
553
|
+
decision_reason: typeof record.decision_reason === "string" ? record.decision_reason : "unknown",
|
|
554
|
+
enforcement_point: typeof record.enforcement_point === "string" ? record.enforcement_point : "unknown",
|
|
555
|
+
harness_runtime_context: {
|
|
556
|
+
agent_id: typeof harnessRuntimeContext.agent_id === "string" ? harnessRuntimeContext.agent_id : typeof record.agent_id === "string" ? record.agent_id : "unknown",
|
|
557
|
+
agent_role: typeof harnessRuntimeContext.agent_role === "string" ? harnessRuntimeContext.agent_role : typeof record.agent_role === "string" ? record.agent_role : "unknown",
|
|
558
|
+
agent_type: typeof harnessRuntimeContext.agent_type === "string" ? harnessRuntimeContext.agent_type : typeof record.agent_type === "string" ? record.agent_type : "unknown",
|
|
559
|
+
session_agent: typeof harnessRuntimeContext.session_agent === "string" ? harnessRuntimeContext.session_agent : typeof record.session_agent === "string" ? record.session_agent : "unknown",
|
|
560
|
+
harness_adapter: typeof harnessRuntimeContext.harness_adapter === "string" ? harnessRuntimeContext.harness_adapter : typeof record.harness_adapter === "string" ? record.harness_adapter : "unknown",
|
|
561
|
+
runtime: typeof harnessRuntimeContext.runtime === "string" ? harnessRuntimeContext.runtime : typeof record.runtime === "string" ? record.runtime : "unknown",
|
|
562
|
+
},
|
|
563
|
+
session_id: typeof record.session_id === "string" ? record.session_id : "unknown",
|
|
564
|
+
run_id: typeof record.run_id === "string" ? record.run_id : "unknown",
|
|
565
|
+
step_id: typeof record.step_id === "string" ? record.step_id : "unknown",
|
|
566
|
+
tool_name: typeof record.tool_name === "string" ? record.tool_name : "unknown",
|
|
567
|
+
action_kind: typeof record.action_kind === "string" ? record.action_kind : "unknown",
|
|
568
|
+
resource_ref: typeof record.resource_ref === "string" ? record.resource_ref : "unknown",
|
|
569
|
+
evidence_refs: evidenceRefs.length > 0 ? evidenceRefs : [{ type: "unknown", ref: "unknown" }],
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
export async function readRuntimeDecisionEvents(projectDir, days = 1, sessionId) {
|
|
573
|
+
const entries = [];
|
|
574
|
+
if (sessionId) {
|
|
575
|
+
entries.push(...await readRuntimeDecisionFile(join(resolveStateDir(projectDir, sessionId), "runtime-decisions.jsonl")));
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
entries.push(...await readGlobalRuntimeDecisionEvents(projectDir, days));
|
|
579
|
+
}
|
|
580
|
+
return entries.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
581
|
+
}
|
|
582
|
+
async function readRuntimeDecisionFile(filePath) {
|
|
583
|
+
const entries = [];
|
|
584
|
+
try {
|
|
585
|
+
const content = await readFile(filePath, "utf-8");
|
|
586
|
+
for (const line of content.trim().split("\n")) {
|
|
587
|
+
if (!line)
|
|
588
|
+
continue;
|
|
589
|
+
try {
|
|
590
|
+
const event = normalizeRuntimeDecisionEvent(JSON.parse(line));
|
|
591
|
+
if (event)
|
|
592
|
+
entries.push(event);
|
|
593
|
+
}
|
|
594
|
+
catch { /* skip malformed JSON */ }
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
catch { /* file doesn't exist */ }
|
|
598
|
+
return entries;
|
|
599
|
+
}
|
|
600
|
+
async function readGlobalRuntimeDecisionEvents(projectDir, days) {
|
|
601
|
+
const dir = join(projectDir, ".dna", "state", RUNTIME_DECISION_DIR);
|
|
602
|
+
const entries = [];
|
|
603
|
+
const cutoff = new Date();
|
|
604
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
605
|
+
const cutoffDate = cutoff.toISOString().slice(0, 10);
|
|
606
|
+
try {
|
|
607
|
+
const files = (await readdir(dir))
|
|
608
|
+
.filter(f => f.startsWith("runtime-decision-") && f.endsWith(".jsonl"))
|
|
609
|
+
.filter(f => f.slice("runtime-decision-".length, "runtime-decision-".length + 10) >= cutoffDate)
|
|
610
|
+
.sort();
|
|
611
|
+
for (const file of files)
|
|
612
|
+
entries.push(...await readRuntimeDecisionFile(join(dir, file)));
|
|
613
|
+
}
|
|
614
|
+
catch { /* no decisions yet */ }
|
|
615
|
+
return entries;
|
|
616
|
+
}
|
|
449
617
|
export async function readTraces(projectDir, days = 1, sessionId) {
|
|
450
618
|
const entries = [];
|
|
451
619
|
if (sessionId) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/mcp/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { createStateTools } from "./tools-state.js";
|
|
|
15
15
|
import { createCompileTools } from "./tools-compile.js";
|
|
16
16
|
import { createEnforceTools } from "./tools-enforce.js";
|
|
17
17
|
import { createObservabilityTools } from "./tools-observability.js";
|
|
18
|
+
import { createContextTools } from "./tools-context.js";
|
|
18
19
|
// Parse args
|
|
19
20
|
const args = process.argv.slice(2);
|
|
20
21
|
let projectDir = process.cwd();
|
|
@@ -36,6 +37,7 @@ catch { /* use default */ }
|
|
|
36
37
|
const tools = [
|
|
37
38
|
...createStateTools(projectDir),
|
|
38
39
|
...createCompileTools(projectDir),
|
|
40
|
+
...createContextTools(projectDir),
|
|
39
41
|
...createEnforceTools(projectDir),
|
|
40
42
|
...createObservabilityTools(projectDir),
|
|
41
43
|
];
|
|
@@ -7,45 +7,8 @@
|
|
|
7
7
|
* - dna_sync: Run full sync pipeline (compile + inject + hooks)
|
|
8
8
|
*/
|
|
9
9
|
import { resolve } from "node:path";
|
|
10
|
-
import {
|
|
11
|
-
import { loadDNA, compileFromFiles } from "../compiler/index.js";
|
|
10
|
+
import { compileFromFiles, detectDNAConfigs, expandDNAInputFiles, formatDiagnostic, loadDNAWithDiagnostics } from "../compiler/index.js";
|
|
12
11
|
import { textResult, errorResult } from "./server.js";
|
|
13
|
-
async function fileExists(path) {
|
|
14
|
-
try {
|
|
15
|
-
await stat(path);
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
/** Auto-detect DNA config files in the project directory. */
|
|
23
|
-
async function detectConfigs(projectDir) {
|
|
24
|
-
const { readdir } = await import("node:fs/promises");
|
|
25
|
-
// Multi-config: .dna/configs/*.yaml
|
|
26
|
-
const configsDir = resolve(projectDir, ".dna", "configs");
|
|
27
|
-
try {
|
|
28
|
-
const files = await readdir(configsDir);
|
|
29
|
-
const yamls = files
|
|
30
|
-
.filter(f => f.endsWith(".yaml") || f.endsWith(".yml"))
|
|
31
|
-
.sort()
|
|
32
|
-
.map(f => resolve(configsDir, f));
|
|
33
|
-
if (yamls.length > 0)
|
|
34
|
-
return yamls;
|
|
35
|
-
}
|
|
36
|
-
catch { /* dir doesn't exist */ }
|
|
37
|
-
// Legacy single-config
|
|
38
|
-
const candidates = [
|
|
39
|
-
".dna/config.yaml", ".dna/config.yml", ".dna/config.json",
|
|
40
|
-
".dna.yaml", ".dna.yml", ".dna.json",
|
|
41
|
-
];
|
|
42
|
-
for (const name of candidates) {
|
|
43
|
-
const path = resolve(projectDir, name);
|
|
44
|
-
if (await fileExists(path))
|
|
45
|
-
return [path];
|
|
46
|
-
}
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
12
|
export function createCompileTools(projectDir) {
|
|
50
13
|
return [
|
|
51
14
|
// ── dna_validate ─────────────────────────────────────
|
|
@@ -62,20 +25,23 @@ export function createCompileTools(projectDir) {
|
|
|
62
25
|
const configPath = typeof args.config_path === "string"
|
|
63
26
|
? resolve(projectDir, args.config_path)
|
|
64
27
|
: undefined;
|
|
65
|
-
const files = configPath ? [configPath] : await
|
|
28
|
+
const files = configPath ? [configPath] : await detectDNAConfigs(projectDir);
|
|
66
29
|
if (files.length === 0)
|
|
67
30
|
return errorResult("No DNA config files found.");
|
|
68
31
|
const results = [];
|
|
69
32
|
let allValid = true;
|
|
70
33
|
for (const file of files) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
34
|
+
const result = await loadDNAWithDiagnostics(file);
|
|
35
|
+
const errors = result.diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
36
|
+
if (errors.length > 0 || !result.dna) {
|
|
37
|
+
allValid = false;
|
|
38
|
+
results.push(`${file}: INVALID`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
74
41
|
results.push(`${file}: valid`);
|
|
75
42
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
results.push(`${file}: INVALID — ${err instanceof Error ? err.message : String(err)}`);
|
|
43
|
+
for (const diagnostic of result.diagnostics) {
|
|
44
|
+
results.push(` ${formatDiagnostic(diagnostic)}`);
|
|
79
45
|
}
|
|
80
46
|
}
|
|
81
47
|
results.unshift(allValid ? "All configs valid." : "Validation errors found.");
|
|
@@ -98,11 +64,12 @@ export function createCompileTools(projectDir) {
|
|
|
98
64
|
const configPath = typeof args.config_path === "string"
|
|
99
65
|
? resolve(projectDir, args.config_path)
|
|
100
66
|
: undefined;
|
|
101
|
-
const files = configPath ? [configPath] : await
|
|
67
|
+
const files = configPath ? [configPath] : await detectDNAConfigs(projectDir);
|
|
102
68
|
if (files.length === 0)
|
|
103
69
|
return errorResult("No DNA config files found.");
|
|
104
70
|
try {
|
|
105
|
-
const
|
|
71
|
+
const expandedFiles = await expandDNAInputFiles(files);
|
|
72
|
+
const ir = await compileFromFiles(expandedFiles, {
|
|
106
73
|
context: typeof args.context === "string" ? args.context : undefined,
|
|
107
74
|
role: typeof args.role === "string" ? args.role : undefined,
|
|
108
75
|
});
|
|
@@ -142,12 +109,13 @@ export function createCompileTools(projectDir) {
|
|
|
142
109
|
evolve: { type: "boolean", description: "Run evolution engine before compiling (default false)" },
|
|
143
110
|
context: { type: "string", description: "Active context name" },
|
|
144
111
|
role: { type: "string", description: "Active role name" },
|
|
112
|
+
mode: { type: "string", enum: ["auto", "plugin", "bin"], description: "Sync mode (default auto)" },
|
|
145
113
|
},
|
|
146
114
|
},
|
|
147
115
|
handler: async (args) => {
|
|
148
116
|
// Import sync command dynamically to avoid circular deps
|
|
149
|
-
const { runSync
|
|
150
|
-
const files = await
|
|
117
|
+
const { runSync } = await import("../cli/commands/sync.js");
|
|
118
|
+
const files = await detectDNAConfigs(projectDir);
|
|
151
119
|
if (files.length === 0)
|
|
152
120
|
return errorResult("No DNA config files found.");
|
|
153
121
|
const exitCode = await runSync({
|
|
@@ -160,6 +128,7 @@ export function createCompileTools(projectDir) {
|
|
|
160
128
|
skillsDir: resolve(projectDir, ".claude", "skills"),
|
|
161
129
|
settingsPath: resolve(projectDir, ".claude", "settings.json"),
|
|
162
130
|
remove: false,
|
|
131
|
+
mode: typeof args.mode === "string" && ["auto", "plugin", "bin"].includes(args.mode) ? args.mode : "auto",
|
|
163
132
|
});
|
|
164
133
|
if (exitCode === 0) {
|
|
165
134
|
return textResult("Sync completed successfully.");
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { findContextSource, formatContextSource, loadContextSourceIndex, readContextSourceDocument, searchContextSources, } from "../runtime/context-sources.js";
|
|
3
|
+
import { errorResult, textResult } from "./server.js";
|
|
4
|
+
function filesArg(args, projectDir) {
|
|
5
|
+
if (typeof args.config_path === "string")
|
|
6
|
+
return [resolve(projectDir, args.config_path)];
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
export function createContextTools(projectDir) {
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
name: "dna_context_sources",
|
|
13
|
+
description: "List explicitly declared planning-only advisory context sources",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
config_path: { type: "string", description: "Path to DNA config (auto-detected if omitted)" },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
handler: async (args) => {
|
|
21
|
+
const index = await loadContextSourceIndex(projectDir, filesArg(args, projectDir));
|
|
22
|
+
if (index.sources.length === 0)
|
|
23
|
+
return textResult("No advisory context sources declared.");
|
|
24
|
+
const lines = index.sources.map(formatContextSource);
|
|
25
|
+
if (index.planning_instructions.length > 0) {
|
|
26
|
+
lines.push("", "Planning instructions:");
|
|
27
|
+
for (const instruction of index.planning_instructions)
|
|
28
|
+
lines.push(`- ${instruction}`);
|
|
29
|
+
}
|
|
30
|
+
return textResult(lines.join("\n"));
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "dna_context_search",
|
|
35
|
+
description: "Search advisory context source metadata. This does not search repository files.",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
query: { type: "string", description: "Metadata query" },
|
|
40
|
+
config_path: { type: "string", description: "Path to DNA config (auto-detected if omitted)" },
|
|
41
|
+
},
|
|
42
|
+
required: ["query"],
|
|
43
|
+
},
|
|
44
|
+
handler: async (args) => {
|
|
45
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
46
|
+
const index = await loadContextSourceIndex(projectDir, filesArg(args, projectDir));
|
|
47
|
+
const matches = searchContextSources(index, query);
|
|
48
|
+
if (matches.length === 0)
|
|
49
|
+
return errorResult(`No advisory context sources matched '${query}'.`);
|
|
50
|
+
return textResult(matches.map(formatContextSource).join("\n"));
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "dna_get_context",
|
|
55
|
+
description: "Read a declared local advisory context document by source id",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
id: { type: "string", description: "Context source id" },
|
|
60
|
+
config_path: { type: "string", description: "Path to DNA config (auto-detected if omitted)" },
|
|
61
|
+
},
|
|
62
|
+
required: ["id"],
|
|
63
|
+
},
|
|
64
|
+
handler: async (args) => {
|
|
65
|
+
const id = typeof args.id === "string" ? args.id : "";
|
|
66
|
+
const index = await loadContextSourceIndex(projectDir, filesArg(args, projectDir));
|
|
67
|
+
const source = findContextSource(index, id);
|
|
68
|
+
if (!source)
|
|
69
|
+
return errorResult(`Unknown advisory context source '${id}'.`);
|
|
70
|
+
const doc = await readContextSourceDocument(projectDir, source);
|
|
71
|
+
const lines = [
|
|
72
|
+
formatContextSource(source),
|
|
73
|
+
"Planning-only advisory context; does not satisfy enforcement, handoff, or ArtifactManifest requirements.",
|
|
74
|
+
];
|
|
75
|
+
if (doc.content !== undefined) {
|
|
76
|
+
lines.push("", "---", doc.content);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
lines.push("No local document body is available for this source type.");
|
|
80
|
+
}
|
|
81
|
+
return textResult(lines.join("\n"));
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Intent DNA — MCP Enforce Tools (U2)
|
|
3
3
|
*
|
|
4
|
-
* Provides enforcement via MCP server with in-memory IR cache.
|
|
5
|
-
*
|
|
4
|
+
* Provides enforcement via a persistent MCP server with in-memory IR cache.
|
|
5
|
+
* MCP calls reuse the full hook runtime without reloading IR on every request.
|
|
6
6
|
*
|
|
7
7
|
* Tools:
|
|
8
8
|
* - dna_enforce: Run enforcement for a hook event (PreToolUse, PostToolUse, etc.)
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Intent DNA — MCP Enforce Tools (U2)
|
|
3
3
|
*
|
|
4
|
-
* Provides enforcement via MCP server with in-memory IR cache.
|
|
5
|
-
*
|
|
4
|
+
* Provides enforcement via a persistent MCP server with in-memory IR cache.
|
|
5
|
+
* MCP calls reuse the full hook runtime without reloading IR on every request.
|
|
6
6
|
*
|
|
7
7
|
* Tools:
|
|
8
8
|
* - dna_enforce: Run enforcement for a hook event (PreToolUse, PostToolUse, etc.)
|
|
9
9
|
*/
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
11
|
import { stat, readFile } from "node:fs/promises";
|
|
12
|
-
import {
|
|
12
|
+
import { silentOutput } from "../hooks/protocol.js";
|
|
13
13
|
import { hookEventsForSurface } from "../hooks/event-registry.js";
|
|
14
|
+
import { validateHookInput } from "../hooks/schema.js";
|
|
15
|
+
import { runHookEvent } from "../hooks/cli.js";
|
|
14
16
|
import { textResult, errorResult } from "./server.js";
|
|
15
17
|
// ── IR Cache ────────────────────────────────────────────────
|
|
16
18
|
/** In-memory IR cache with mtime-based invalidation. */
|
|
@@ -63,49 +65,17 @@ export class IRCache {
|
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
const VALID_EVENTS = new Set(hookEventsForSurface("mcp"));
|
|
66
|
-
function dispatchEnforce(event, ir, input, roles) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
cwd: typeof input.cwd === "string" ? input.cwd : undefined,
|
|
78
|
-
}, undefined, roles);
|
|
79
|
-
case "PostToolUse":
|
|
80
|
-
return enforcePostToolUse(ir, {
|
|
81
|
-
tool_name: String(input.tool_name ?? ""),
|
|
82
|
-
tool_input: (input.tool_input ?? {}),
|
|
83
|
-
tool_output: typeof input.tool_output === "string" ? input.tool_output : undefined,
|
|
84
|
-
agent_type: typeof input.agent_type === "string" ? input.agent_type : undefined,
|
|
85
|
-
});
|
|
86
|
-
case "UserPromptSubmit":
|
|
87
|
-
return enforceUserPromptSubmit(ir, {
|
|
88
|
-
prompt: typeof input.prompt === "string" ? input.prompt : undefined,
|
|
89
|
-
});
|
|
90
|
-
case "SubagentStop":
|
|
91
|
-
return enforceSubagentStop(ir, {
|
|
92
|
-
agent_name: typeof input.agent_name === "string" ? input.agent_name : undefined,
|
|
93
|
-
agent_type: typeof input.agent_type === "string" ? input.agent_type : undefined,
|
|
94
|
-
});
|
|
95
|
-
case "PreCompact":
|
|
96
|
-
return enforcePreCompact(ir);
|
|
97
|
-
case "Notification":
|
|
98
|
-
return enforceNotification(ir, {
|
|
99
|
-
title: typeof input.title === "string" ? input.title : undefined,
|
|
100
|
-
message: typeof input.message === "string" ? input.message : undefined,
|
|
101
|
-
});
|
|
102
|
-
case "SessionStart":
|
|
103
|
-
return enforceSessionStart(ir, {
|
|
104
|
-
cwd: typeof input.cwd === "string" ? input.cwd : undefined,
|
|
105
|
-
sessionId: typeof input.session_id === "string" ? input.session_id : undefined,
|
|
106
|
-
});
|
|
107
|
-
default:
|
|
108
|
-
return null;
|
|
68
|
+
async function dispatchEnforce(event, ir, input, projectDir, roles) {
|
|
69
|
+
try {
|
|
70
|
+
const validation = validateHookInput(event, input);
|
|
71
|
+
if (!validation.valid)
|
|
72
|
+
return silentOutput();
|
|
73
|
+
const rawInput = validation.normalized;
|
|
74
|
+
const sessionId = typeof rawInput.sessionId === "string" ? rawInput.sessionId : undefined;
|
|
75
|
+
return await runHookEvent({ event, ir, rawInput, projectDir, sessionId, roles });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return silentOutput();
|
|
109
79
|
}
|
|
110
80
|
}
|
|
111
81
|
// ── Tool Registration ───────────────────────────────────────
|
|
@@ -114,8 +84,8 @@ export function createEnforceTools(projectDir) {
|
|
|
114
84
|
return [
|
|
115
85
|
{
|
|
116
86
|
name: "dna_enforce",
|
|
117
|
-
description: "Run DNA enforcement for a hook event
|
|
118
|
-
"
|
|
87
|
+
description: "Run DNA enforcement for a hook event inside the persistent MCP server. " +
|
|
88
|
+
"Uses cached IR and the same runtime path as the dna-hook binary.",
|
|
119
89
|
inputSchema: {
|
|
120
90
|
type: "object",
|
|
121
91
|
properties: {
|
|
@@ -141,7 +111,7 @@ export function createEnforceTools(projectDir) {
|
|
|
141
111
|
return errorResult("No compiled IR found. Run `dna sync` to compile your DNA config.");
|
|
142
112
|
}
|
|
143
113
|
const input = (args.input ?? {});
|
|
144
|
-
const result = dispatchEnforce(event, cached.ir, input, cached.roles);
|
|
114
|
+
const result = await dispatchEnforce(event, cached.ir, input, projectDir, cached.roles);
|
|
145
115
|
// Format as human-readable text
|
|
146
116
|
const lines = [];
|
|
147
117
|
lines.push(`Decision: ${result.continue ? "allow" : "BLOCK"}`);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { textResult } from "./server.js";
|
|
2
2
|
import { buildKernelReport, formatKernelReport } from "../report/kernel-report.js";
|
|
3
|
+
import { buildReportPackage } from "../report/report-package.js";
|
|
3
4
|
export function createObservabilityTools(projectDir) {
|
|
4
5
|
return [
|
|
5
6
|
{
|
|
6
7
|
name: "dna_kernel_report",
|
|
7
|
-
description: "Observability-only local kernel report. Reads traces/verifier results
|
|
8
|
+
description: "Observability-only local kernel report. Reads RuntimeDecisionEvent as primary enterprise evidence; legacy traces/verifier results are diagnostic-only. Does not execute verifiers or mutate state.",
|
|
8
9
|
inputSchema: {
|
|
9
10
|
type: "object",
|
|
10
11
|
properties: {
|
|
@@ -27,7 +28,30 @@ export function createObservabilityTools(projectDir) {
|
|
|
27
28
|
return textResult(JSON.stringify(report, null, 2));
|
|
28
29
|
}
|
|
29
30
|
return textResult(formatKernelReport(report) +
|
|
30
|
-
"\nMCP observability only:
|
|
31
|
+
"\nMCP observability only: RuntimeDecisionEvent is primary enterprise evidence; legacy traces/verifiers are diagnostic-only. This tool does not execute verifiers, does not enforce hook parity, and does not mutate state or markers.\n");
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "dna_report_package",
|
|
36
|
+
description: "Read-only local evidence package with RuntimeDecisionEvent primary evidence, diagnostic legacy traces/verifier results/handoffs, deferrals, and fact-backed critical decisions.",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
days: { type: "number", description: "Days to look back (default 7)" },
|
|
41
|
+
session_id: { type: "string", description: "Session ID filter" },
|
|
42
|
+
dna_id: { type: "string", description: "DNA ID for marker preview; derived from compiled IR if omitted and unambiguous" },
|
|
43
|
+
include_marker_preview: { type: "boolean", description: "Include preview-only marker changes (default false)" },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
handler: async (args) => {
|
|
47
|
+
const reportPackage = await buildReportPackage({
|
|
48
|
+
projectDir,
|
|
49
|
+
days: typeof args.days === "number" ? args.days : 7,
|
|
50
|
+
sessionId: typeof args.session_id === "string" ? args.session_id : undefined,
|
|
51
|
+
dnaId: typeof args.dna_id === "string" ? args.dna_id : undefined,
|
|
52
|
+
includeMarkerPreview: typeof args.include_marker_preview === "boolean" ? args.include_marker_preview : false,
|
|
53
|
+
});
|
|
54
|
+
return textResult(JSON.stringify(reportPackage, null, 2));
|
|
31
55
|
},
|
|
32
56
|
},
|
|
33
57
|
];
|