@jaimevalasek/aioson 1.16.0 → 1.17.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,55 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.17.2] - 2026-05-22
8
+
9
+ ### Security
10
+ - **Neural Chain — fixes for the 3 @pentester findings against v1.17.1** (SF-NC-01 HIGH + SF-NC-02 MEDIUM + SF-NC-03 LOW). Single consolidated patch closing the `block` recommendation that prevented npm publish of v1.17.1.
11
+ - **SF-NC-01 (HIGH) FIXED — Noise file injection via newline in chain_edges.target_path.** The @pentester probe demonstrated that a crafted row (`target_path = "legit.js\n- [ ] [AUTO-FIXABLE] /etc/passwd ..."`) bypassed the BR-NC-03 `guarded` mode guarantee because `serializeItem` interpolated the path raw and `parseItems` accepted the resulting injected line as a standalone item. New `src/neural-chain-sanitize.js#isUnsafePath` centralizes the rule: reject strings with any ASCII control char (`\x00-\x1f` + `\x7f`, includes `\n` `\r` `\t` `\0`), empty strings, and strings longer than 4096 chars. Wired at three boundaries — **Layer B ingest:** `deriveSessionPairs` (in `agent-ingest.js`) and `computeCoEditPairs` (in `git-ingest.js`) filter unsafe paths before INSERT; **Layer A render:** `flattenAudits` (in `noise-file.js`) drops items with unsafe `target_path` / `source_file` before they reach the noise file body (defense in depth for pre-v1.17.2 rows that may still be active in the database); **CLI boundary:** `runChainAudit` returns `{ ok: false, reason: 'unsafe_file_path' }` when the input file argument fails validation, before the SQL bind. The regression test reproduces the original probe with the same malicious INSERT and asserts the forged `[AUTO-FIXABLE]` line never appears in the rendered body — `guarded` mode safety contract restored.
12
+ - **SF-NC-02 (MEDIUM) FIXED (app-layer only) — chain_edges schema validation gaps.** Same `isUnsafePath` helper covers the length cap (4096) and control-char rejection at ingest, providing the same protection as a schema CHECK without requiring a table rebuild. Schema-level CHECK constraints on `source_path` / `target_path` / `start_at` / `last_seen_at` are deferred to M2 graph maintenance, which already needs a `schema_meta` migration. Application code only writes ISO 8601 timestamps via `new Date().toISOString()` — a malicious direct INSERT could still bypass the timestamp format check at the SQL layer; this is documented as the open M2 follow-up and noted in `requirements-neural-chain.md`. The chain_edges INSERTs from `git_co_edit` and `agent_event` paths are now both protected.
13
+ - **SF-NC-03 (LOW) FIXED — normalizeThreshold rejects negative zero + spec trust-boundary note.** `normalizeThreshold` now returns `null` when the parsed value is `-0` via `Object.is(n, -0)` check — required because `n < 0` evaluates `false` for `-0`. A crafted `.aioson/config.md` with `chain_auto_threshold: -0` now falls back to the default `0.8`. `requirements-neural-chain.md` EC-NC-07 amended with an explicit trust-boundary note: `.aioson/config.md` must remain under version control + code review; `.gitignore` on it is an anti-pattern for neural-chain. Runtime warning telemetry when `autonomy=autonomous + threshold=0` is documented as a future hardening but not shipped (low ROI given the doc note covers the operational concern).
14
+
15
+ ### Notes
16
+ - **Cumulative regression**: 2780 tests, 2777 pass, 1 skipped, 2 fail (AC-P1-07 operator-memory pre-existing + AC-ALL-101 perf flake intermittent on Windows — both unrelated). +5 new tests in `tests/neural-chain-invariants.test.js` covering all three SF-NC fixes plus a Layer B unit check on `deriveSessionPairs`.
17
+ - **`security-findings-neural-chain.json`** updated — all three findings now carry `status: fixed`, `fix_release: v1.17.2`, and a `fix_summary` describing exactly what landed. `@qa` is the final decision owner per `pentester.md` ownership protocol and should re-verify before treating the findings as closed. A re-run of the @pentester probes against v1.17.2 is recommended to confirm mitigation in addition to the regression tests.
18
+ - **`npm publish` unblocked**: v1.17.1 was tagged but the @pentester block recommendation prevented publishing it. v1.17.2 supersedes pre-publish; user chooses this tag for npm publish.
19
+ - **Inception loop closed for this cycle**: @qa flagged 2 Medium → @dev hotfixed in v1.17.1 → @tester defensive invariants caught a third bug (M-003 schema drift) → @dev fixed → @pentester adversarial review found 3 more (SF-NC-01..03) → @dev fixed in this v1.17.2 release. Each agent role surfaced a class of problem the previous role could not have caught — exactly the loop neural-chain itself is designed to support for *user* code.
20
+
21
+ ## [1.17.1] - 2026-05-22
22
+
23
+ ### Fixed
24
+ - **Neural Chain — hotfix for 3 Medium findings from `@qa` Gate D + `@tester` gap-fill (M-01 / M-02 / M-003).** Consolidated patch — single release closing the residual risks documented in `spec-neural-chain.md` § QA sign-off + `test-plan.md` § bug-found.
25
+ - **M-02 (bug-found-002) FIXED — BR-NC-01 dual-source dedupe.** When the same `(source_path, target_path)` pair existed under both `edge_type='git_co_edit'` AND `edge_type='agent_event'`, `queryImpacts` and `chain:audit` previously returned both rows separately, duplicating the same target in noise files (different motivos). Spec BR-NC-01 says "reportar `max(c_git, c_event)` — não soma; evita double-count entre fontes". Both SQL queries (in `src/neural-chain-agent-ingest.js#queryImpacts` and `src/commands/chain-audit.js`) now wrap the row scan in a SQLite window function `ROW_NUMBER() OVER (PARTITION BY target_path ORDER BY confidence DESC, hit_count DESC, last_seen_at DESC)` and keep only `rn = 1`. The chosen `edge_type` is the one from the row that won the max confidence (tiebreaker by hit_count then last_seen_at). 2 new tests in `tests/neural-chain-invariants.test.js` cover both call sites (hook + CLI) with a dual-source seed asserting the deduped row reports the max confidence (0.9, not 0.6+0.9) and the surviving edge_type.
26
+ - **M-003 (bug-found-003) FIXED — chain_audit telemetry schema drift between emitters.** Previously the CLI emitter (`chain-audit.js`) and the hook emitter (`agent-ingest.js`) drifted on payload fields: CLI was missing `noise_file`/`auto_fixable_count`/`tokens_used`; hook EC-NC-05 no-op event was missing `duration_ms`/`error`; both used singular `source_file` instead of the spec'd plural `source_files`; `tokens_used` was never populated by anyone. New `src/neural-chain-telemetry.js` exposes a single `emitChainAuditEvent(db, { agent, message, ...payload })` helper that builds the full 8-field BR-NC-10 payload schema (`feature_slug, source_files[], impacts_found, auto_fixable_count, noise_file, tokens_used, duration_ms, error`) with sane defaults for the no-op path. Both call sites migrated. CLI passes `source_files: [filePath]` (singleton array) so the spec'd plural shape holds; hook passes the full session's `safeArtifacts`. `tokens_used` ships as `0` placeholder in V1 — re-instrument when LLM-mediated path activates (M2 concern). Legacy singular `source_file` alias preserved in both emit payloads to keep any v1.17.0 dashboard query working until v2. `tests/neural-chain-invariants.test.js` A.2 promoted from a 2-field subset check to the full 8-field BR-NC-10 schema validation, with type discipline (source_files is array, duration_ms is number, etc.) on both hook and CLI events.
27
+ - **M-01 (bug-found-001) AMENDED — EC-NC-04 retry/backoff acceptably deferred in V1.** Spec EC-NC-04 + requirements EC-NC-04 + this CHANGELOG entry now explicitly acknowledge that V1 ships single-attempt try/catch instead of the spec'd 3-attempt exponential backoff. Justification: BR-NC-11 (non-blocking) is the load-bearing contract — audit failure never propagates to `runAgentDone`, agent:done completes normally regardless. The `runAgentDone` path is sequential with low contention (Living Memory reflect-prepare + Neural Chain hook run in series, no real lock pressure). The `withRetry({ attempts: 3, backoffMs: [100, 200, 500] })` helper is deferred to M1.5/M2 when squad-mode concurrent edits (EC-NC-08) actually create lock contention. Zero code change for this item — pure spec amendment.
28
+
29
+ ### Notes
30
+ - **Cumulative regression**: 2775 tests, 2772 pass, 1 skipped, 2 fail (AC-P1-07 operator-memory pre-existing + AC-ALL-101 perf flake intermittent on Windows — both documented, unrelated to this hotfix). +2 tests vs v1.17.0 baseline.
31
+ - **AC-AUDIT-NC**: still 7/7 satisfied; this hotfix tightens the BR-NC-01 + BR-NC-10 contracts in code, not in scope.
32
+ - **No version bump for npm publish needed yet** — v1.17.0 has NOT been published. v1.17.1 supersedes it pre-publish. User chooses which tag to `npm publish` from when ready.
33
+ - **Bug discovery loop closed**: `@qa` flagged M-01 + M-02 in Gate D residual; `@tester` discovered M-003 via the A.2 schema completeness invariant test (test had to relax its assertion because the no-op event omitted `duration_ms` — that relaxation itself became the smoking gun); `@dev` consolidated all three in this single patch slice.
34
+
35
+ ## [1.17.0] - 2026-05-21
36
+
37
+ ### Added
38
+ - **Neural Chain — Phase 1 shipped end-to-end (Slices 1-6).** Impact-aware code editing for AIOSON: when an agent edits a file, the post-session hook audits chain edges (git co-edit + agent-event signals) and surfaces files that may need updating via a per-session noise file consumed by `@neo` as a blocker.
39
+ - **Schema (Slice 1)**: `chain_edges` table in `aios.sqlite` — 10 fields, 3 indexes (2 lookup + 1 partial UNIQUE on active rows for archive-flow per BR-NC-08), CHECK constraints on `edge_type` ∈ {git_co_edit, agent_event} + `confidence` ∈ [0,1] + `hit_count > 0`. New `src/neural-chain-migration.js` idempotent runner wired downstream of `runLearningLoopMigration` in `runtime-store.js#ensureLegacyColumns`.
40
+ - **`aioson chain:audit <file> [--feature=<slug>] [--json] [--limit=N]` (Slice 2)**: read-only CLI returning top-N active impacts ordered by confidence DESC (default 20, hard cap 200). Emits one `execution_events` row per invocation with `event_type='chain_audit'` (BR-NC-10 telemetry obligation). Failure non-blocking per BR-NC-11. i18n keys added in 4 locales.
41
+ - **Git co-edit ingest helper (Slice 2)**: `src/neural-chain-git-ingest.js` — pure `parseGitLog` / `computeCoEditPairs` / `ingestGitCoEditEdges` plus `runGitIngest` integration wrapper. BR-NC-01 saturation at 10 co-edits, BR-NC-08 hard cap 10k per source via archive-oldest-by-`last_seen_at`, 90-day window filter, mega-commits (>50 files) + `.aioson/*` paths excluded, UPSERT respecting partial UNIQUE index. EC-NC-06 honored (skip when git history < 50 commits).
42
+ - **Agent-event ingest hook (Slice 3)**: `src/neural-chain-agent-ingest.js` — `deriveSessionPairs` / `ingestAgentEventEdges` / `runChainHookOnAgentDone` / `queryImpacts`. Wired into both `live_event` and `standalone` branches of `runAgentDone` in `src/commands/runtime.js` (best-effort try/catch envelope, BR-NC-11). BR-NC-01 saturation at 5 hits via UPSERT ON CONFLICT incrementing `hit_count` + recomputing confidence atomically. EC-NC-05 explicitly honored — empty/single-file artifact lists still emit exactly one `chain_audit` event with `impacts_found=0` so the guardrail metric series stays continuous.
43
+ - **Noise file write/lifecycle (Slice 4)**: `src/neural-chain-noise-file.js` — `writeNoiseFile`, `readNoiseFileAndRecompute`, `maybeDeleteNoiseFile` (sync fs, no new dependency). Path scheme `.aioson/context/noises/{feature-slug}-{YYYYMMDD-HHMM}.md` with `unspecified-{ts}.md` fallback (BR-NC-06). YAML frontmatter carries `{slug, edit_at, autonomy_mode, source_files, total_items, resolved_items}`; body lists `- [ ] {target} — {edge_type} {confidence} (source: {file})` items, file-level only (BR-NC-09; M1 forbids `:symbol` granularity). EC-NC-09 (corrupted frontmatter still returns parsed body items) + EC-NC-10 (idempotent unlink on race delete) honored.
44
+ - **`@neo` noise blocker step (Slice 5)**: `@neo` activation protocol gains Step 1.5 — detects `.aioson/context/noises/*.md` with pending `- [ ]` items via regex or `readNoiseFileAndRecompute` helper; surfaces as ⛔ blocker with `confidence: low` and `clarification` populated, listing each pending item by target_path + motivo. Resolution path is marking `- [x]` (lazy unlink on next hook invocation per EC-NC-10); explicit skip via natural-language `"skip noises"` with `reason: skipped <N> noise file(s)` in routing block. New top-priority "Chain audit pending" stage in Step 3 takes precedence over all other stages. Mirrored byte-for-byte to `template/.aioson/agents/neo.md` (brain `sheldon-001` template parity verified via `diff -q`).
45
+ - **Autonomy mode wiring + BR-NC-02/03 threshold rules (Slice 6)**: new `src/neural-chain-config.js` exposes `readChainConfig({ targetDir })` returning `{autonomyMode, chainAutoThreshold, source}` from `.aioson/config.md` YAML frontmatter. EC-NC-07 honored in 4 code paths (null targetDir, ENOENT, no frontmatter, invalid value) — defaults `guarded` / 0.8 with no force-edit. New `classifyImpact` applies BR-NC-02 rule (a) test-pair filename match cross-language and rule (c) `confidence > threshold AND edge_type='agent_event' AND hit_count > 5`. **Rule (b) literal identifier match deferred to M1.5/M2** — requires git diff parsing, heavy for V1 with bounded marginal gain. BR-NC-03 mode semantics fully wired: `guarded` → all noise (no marker), `standard` → matches tagged `[AUTO-FIXABLE]`, `autonomous` → matches `[AUTO-FIXABLE]` + non-matches `[AUTO-FIXABLE-BEST-EFFORT]`. Both `standard` and `autonomous` now write the noise file (Slice 4 deferred; Slice 6 enables). Telemetry payload (BR-NC-10) gains `auto_fixable_count` + `chain_auto_threshold`.
46
+ - **`tests/neural-chain-{migration,git-ingest,agent-ingest,noise-file,autonomy}.test.js` + `tests/chain-audit.test.js`** — 81 acceptance tests cumulative across Slices 1-6 (11 + 21 + 12 + 13 + 23 + chain-audit suite). Coverage spans schema CHECK constraints, partial-UNIQUE archive flow, confidence formula + saturation, hard-cap enforcement, UPSERT idempotency, EC-NC-05/06/07/09/10, classifier mode×rule combinations, marker render + parse round-trip, hook integration auto-resolving config + per-mode classification + telemetry completeness.
47
+
48
+ ### Notes
49
+ - **Phase 1 complete.** Neural Chain shipped Slices 1-6 in a single 2026-05-21 dev day (inception-mode pacing: framework feature being implemented using the framework's own agents). Single release v1.17.0 per progressive-release strategy — no per-slice version bumps.
50
+ - **AC-AUDIT-NC done gate 7/7 satisfied** (verification mapping in `spec-neural-chain.md`): item 1 `chain:audit` in `runAgentDone` ✓, item 2 `@neo` surfaces noises as blocker ✓, item 3 autonomy mode read via unit test covering 3 modes ✓, item 4 schema migration applied ✓, item 5 coverage ≥ 80% on critical paths ✓, item 6 CHANGELOG entry ✓ (this release), item 7 template parity (`diff -q .aioson/agents/neo.md template/.aioson/agents/neo.md` returns 0) ✓.
51
+ - **Primary success metric (from PRD)**: −50% second-call correction loops in 30d post-release. **Baseline instrumentation TBD** in next 20-30 sessions; post-shipping delta measured at 30-day mark.
52
+ - **Guardrail metric**: `tokens_used` in `runtime_events` filtered `type='chain_audit'` should stay stable over time. `aioson chain:stats` aggregation planned as follow-up M1.5 feature. Pulse alert when `delta_avg > 2x` month-over-month — signal that M2 graph maintenance (skill LLM-judged + heuristic + `chain:prune`) is due.
53
+ - **Out-of-scope V1, planned for V2/M2**: squad/parallel edit scenarios (EC-NC-08), `chain_node_cap` configurability (hardcoded 10k V1), BR-NC-02 rule (b) literal identifier match via git diff parsing, AST drill-down + multi-language AST via tree-sitter, Obsidian-style graph visualization, `chain:prune` skill + heuristic cleanup.
54
+ - **Brain nodes applied during implementation**: `sheldon-001` (template parity for agent files), `sheldon-005` (CLI-first integration — reused `execution_events` instead of a new table), `sheldon-006` (audit wiring before close — feature was design-complete only until AC-AUDIT-NC passed). All three reinforced as patterns by this feature's shipping cycle.
55
+
7
56
  ## [1.16.0] - 2026-05-21
8
57
 
9
58
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaimevalasek/aioson",
3
- "version": "1.16.0",
3
+ "version": "1.17.2",
4
4
  "description": "AI operating framework for hyper-personalized software.",
5
5
  "keywords": [
6
6
  "ai",
package/src/cli.js CHANGED
@@ -14,6 +14,7 @@ const { runAgentsList, runAgentPrompt } = require('./commands/agents');
14
14
  const { runContextValidate } = require('./commands/context-validate');
15
15
  const { runContextPack } = require('./commands/context-pack');
16
16
  const { runContextLoad } = require('./commands/context-load');
17
+ const { runChainAudit } = require('./commands/chain-audit');
17
18
  const { runMemorySearch } = require('./commands/memory-search');
18
19
  const { runMemoryArchive } = require('./commands/memory-archive');
19
20
  const { runMemoryRestore } = require('./commands/memory-restore');
@@ -242,6 +243,8 @@ const JSON_SUPPORTED_COMMANDS = new Set([
242
243
  'context-pack',
243
244
  'context:load',
244
245
  'context-load',
246
+ 'chain:audit',
247
+ 'chain-audit',
245
248
  'test:smoke',
246
249
  'test-smoke',
247
250
  'test:agents',
@@ -930,6 +933,8 @@ async function main() {
930
933
  result = await runContextPack({ args, options, logger: commandLogger, t });
931
934
  } else if (command === 'context:load' || command === 'context-load') {
932
935
  result = await runContextLoad({ args, options, logger: commandLogger, t });
936
+ } else if (command === 'chain:audit' || command === 'chain-audit') {
937
+ result = await runChainAudit({ args, options, logger: commandLogger, t });
933
938
  } else if (command === 'setup:context' || command === 'setup-context') {
934
939
  result = await runSetupContext({ args, options, logger: commandLogger, t });
935
940
  } else if (command === 'locale:apply' || command === 'locale-apply') {
@@ -0,0 +1,156 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * aioson chain:audit <file> [--limit=N] [--feature=<slug>] [--json]
5
+ *
6
+ * Phase 1 Slice 2 read-only command. Queries `chain_edges` for active edges
7
+ * (`end_at IS NULL`) whose `source_path` matches the given file, ranked by
8
+ * confidence DESC. Emits one row in `execution_events` per invocation
9
+ * (`event_type='chain_audit'`) so the guardrail metric — tokens stable per
10
+ * audit — can be tracked via `runtime:emit` semantics (BR-NC-10).
11
+ *
12
+ * Failure mode (BR-NC-11): SQLite locked / read errors NEVER block the
13
+ * caller. The telemetry row is still emitted with `error` populated and the
14
+ * command returns `{ ok: true, impacts_found: 0, error: <msg> }`. Callers
15
+ * that block on the impact list see "no impacts" and proceed; @neo will
16
+ * surface "last audit failed" on its next activation by reading the latest
17
+ * chain_audit event.
18
+ */
19
+
20
+ const path = require('node:path');
21
+ const { openRuntimeDb } = require('../runtime-store');
22
+ const { emitChainAuditEvent } = require('../neural-chain-telemetry');
23
+ const { isUnsafePath, sanitizationReason } = require('../neural-chain-sanitize');
24
+
25
+ const DEFAULT_LIMIT = 20;
26
+ const HARD_LIMIT_CAP = 200;
27
+
28
+ function normalizeLimit(value) {
29
+ const parsed = Number(value);
30
+ if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_LIMIT;
31
+ if (parsed > HARD_LIMIT_CAP) return HARD_LIMIT_CAP;
32
+ return Math.floor(parsed);
33
+ }
34
+
35
+ async function runChainAudit({ args, options = {}, logger, t }) {
36
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
37
+ const json = Boolean(options.json);
38
+ const filePath = options.file || args[1];
39
+ const featureSlug = options.feature ? String(options.feature).trim() : null;
40
+ const limit = normalizeLimit(options.limit);
41
+
42
+ if (!filePath) {
43
+ const msg = (t && t('chain_audit.file_required')) ||
44
+ 'chain:audit requires a file path. Usage: aioson chain:audit <file> [--limit=N] [--feature=<slug>] [--json]';
45
+ if (logger && typeof logger.log === 'function' && !json) logger.log(msg);
46
+ return { ok: false, reason: 'missing_file' };
47
+ }
48
+
49
+ // SF-NC-01/02 Layer B (CLI boundary) — reject unsafe file paths before
50
+ // they reach SQL bind (which is prepared and safe by itself) AND before
51
+ // they get rendered into the telemetry payload + logger output. Mirrors
52
+ // the ingest-side guard in agent-ingest.js#deriveSessionPairs.
53
+ const filePathReason = sanitizationReason(filePath);
54
+ if (filePathReason !== null) {
55
+ const msg = `chain:audit rejected unsafe file path (${filePathReason})`;
56
+ if (logger && typeof logger.log === 'function' && !json) logger.log(msg);
57
+ return { ok: false, reason: 'unsafe_file_path', rejection_reason: filePathReason };
58
+ }
59
+
60
+ let dbHandle;
61
+ try {
62
+ dbHandle = await openRuntimeDb(targetDir);
63
+ } catch (err) {
64
+ const errMsg = err && err.message ? err.message : String(err);
65
+ if (logger && typeof logger.log === 'function' && !json) {
66
+ logger.log((t && t('chain_audit.runtime_unavailable', { error: errMsg })) ||
67
+ `chain:audit runtime db unavailable: ${errMsg}`);
68
+ }
69
+ return { ok: false, reason: 'runtime_db_unavailable', error: errMsg };
70
+ }
71
+
72
+ const { db } = dbHandle;
73
+ const startedAt = Date.now();
74
+ let impacts = [];
75
+ let auditError = null;
76
+
77
+ try {
78
+ // BR-NC-01 — window function dedupes per target_path so dual-source edges
79
+ // (same (source,target) with both git_co_edit AND agent_event) report
80
+ // max(c_git, c_event) as a single row. Hotfix v1.17.1 — bug-found-002.
81
+ impacts = db.prepare(`
82
+ SELECT target_path, edge_type, confidence, hit_count, last_seen_at
83
+ FROM (
84
+ SELECT target_path, edge_type, confidence, hit_count, last_seen_at,
85
+ ROW_NUMBER() OVER (
86
+ PARTITION BY target_path
87
+ ORDER BY confidence DESC, hit_count DESC, last_seen_at DESC
88
+ ) AS rn
89
+ FROM chain_edges
90
+ WHERE source_path = ? AND end_at IS NULL
91
+ )
92
+ WHERE rn = 1
93
+ ORDER BY confidence DESC, hit_count DESC, last_seen_at DESC
94
+ LIMIT ?
95
+ `).all(filePath, limit);
96
+ } catch (err) {
97
+ auditError = err && err.message ? err.message : String(err);
98
+ }
99
+
100
+ const durationMs = Date.now() - startedAt;
101
+
102
+ emitChainAuditEvent(db, {
103
+ agent: null,
104
+ message: `chain:audit ${filePath} → ${auditError ? 'error' : `${impacts.length} impacts`}`,
105
+ feature_slug: featureSlug,
106
+ source_files: [filePath],
107
+ impacts_found: auditError ? null : impacts.length,
108
+ auto_fixable_count: 0,
109
+ noise_file: null,
110
+ tokens_used: 0,
111
+ duration_ms: durationMs,
112
+ error: auditError,
113
+ // Extra context fields
114
+ limit_applied: limit,
115
+ // Legacy singular alias preserved for backward-compat (removed v2)
116
+ source_file: filePath
117
+ });
118
+
119
+ if (auditError) {
120
+ const msg = (t && t('chain_audit.query_failed', { error: auditError })) ||
121
+ `chain:audit failed to query chain_edges: ${auditError}`;
122
+ if (logger && typeof logger.log === 'function' && !json) logger.log(msg);
123
+ // BR-NC-11: failure non-blocking — still return ok with impacts_found=0
124
+ return {
125
+ ok: true,
126
+ source_file: filePath,
127
+ impacts_found: 0,
128
+ duration_ms: durationMs,
129
+ impacts: [],
130
+ error: auditError
131
+ };
132
+ }
133
+
134
+ if (logger && typeof logger.log === 'function' && !json) {
135
+ if (impacts.length === 0) {
136
+ logger.log((t && t('chain_audit.no_impacts', { file: filePath, duration: durationMs })) ||
137
+ `chain:audit ${filePath} → no impacts detected (${durationMs}ms)`);
138
+ } else {
139
+ logger.log((t && t('chain_audit.results_header', { file: filePath, count: impacts.length, duration: durationMs })) ||
140
+ `chain:audit ${filePath} → ${impacts.length} impact(s) (${durationMs}ms):`);
141
+ for (const row of impacts) {
142
+ logger.log(` ${row.target_path} [${row.edge_type}] confidence=${row.confidence.toFixed(2)} hits=${row.hit_count}`);
143
+ }
144
+ }
145
+ }
146
+
147
+ return {
148
+ ok: true,
149
+ source_file: filePath,
150
+ impacts_found: impacts.length,
151
+ duration_ms: durationMs,
152
+ impacts
153
+ };
154
+ }
155
+
156
+ module.exports = { runChainAudit, DEFAULT_LIMIT, HARD_LIMIT_CAP };
@@ -22,6 +22,7 @@ const { runAutoDelivery } = require('../delivery-runner');
22
22
  const { writeHandoff, buildRuntimeLogHandoff } = require('../session-handoff');
23
23
  const { backupAiosonDocs, isDocCreatingAgent } = require('../backup-local');
24
24
  const { runMemoryReflectPrepare } = require('./memory-reflect-prepare');
25
+ const { runChainHookOnAgentDone } = require('../neural-chain-agent-ingest');
25
26
 
26
27
  const ALLOWED_LAYOUTS = new Set(['document', 'tabs', 'accordion', 'stack', 'mixed']);
27
28
  const DEFAULT_TEXT_FIELDS = ['content', 'text', 'body', 'lyrics', 'markdown'];
@@ -1238,6 +1239,19 @@ async function runAgentDone({ args, options = {}, logger, t }) {
1238
1239
  });
1239
1240
  } catch { /* ignore */ }
1240
1241
 
1242
+ // Neural Chain: best-effort agent_event ingest + per-file audit telemetry.
1243
+ // BR-NC-05 (per-session hook), BR-NC-10 (telemetry obligation), BR-NC-11
1244
+ // (failure non-blocking), EC-NC-05 (no-edits skip path still emits event).
1245
+ try {
1246
+ runChainHookOnAgentDone({
1247
+ db,
1248
+ targetDir,
1249
+ artifacts: artifactPaths,
1250
+ agentName: normalizedAgent,
1251
+ featureSlug: options.feature ? String(options.feature).trim() : null
1252
+ });
1253
+ } catch { /* ignore — never blocks agent_done */ }
1254
+
1241
1255
  return { ok: true, targetDir, dbPath, agent: normalizedAgent, mode: 'live_event', runKey: session.runKey };
1242
1256
  }
1243
1257
 
@@ -1298,6 +1312,19 @@ async function runAgentDone({ args, options = {}, logger, t }) {
1298
1312
  });
1299
1313
  } catch { /* ignore */ }
1300
1314
 
1315
+ // Neural Chain: best-effort agent_event ingest + per-file audit telemetry.
1316
+ // BR-NC-05 (per-session hook), BR-NC-10 (telemetry obligation), BR-NC-11
1317
+ // (failure non-blocking), EC-NC-05 (no-edits skip path still emits event).
1318
+ try {
1319
+ runChainHookOnAgentDone({
1320
+ db,
1321
+ targetDir,
1322
+ artifacts: artifactPaths,
1323
+ agentName: normalizedAgent,
1324
+ featureSlug: options.feature ? String(options.feature).trim() : null
1325
+ });
1326
+ } catch { /* ignore — never blocks agent_done */ }
1327
+
1301
1328
  return { ok: true, targetDir, dbPath, agent: normalizedAgent, mode: 'standalone', runKey, taskKey };
1302
1329
  } finally {
1303
1330
  db.close();
@@ -26,6 +26,15 @@ module.exports = {
26
26
  'aioson context:pack [path] [--agent=<agent>] [--goal=<text>] [--module=<module-or-folder>] [--max-files=8] [--json] [--locale=en]',
27
27
  help_context_load:
28
28
  'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<name> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=en]',
29
+ help_chain_audit:
30
+ 'aioson chain:audit <file> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=en]',
31
+ chain_audit: {
32
+ file_required: 'chain:audit requires a file path. Usage: aioson chain:audit <file> [--limit=N] [--feature=<slug>] [--json]',
33
+ runtime_unavailable: 'chain:audit runtime db unavailable: {error}',
34
+ query_failed: 'chain:audit failed to query chain_edges: {error}',
35
+ no_impacts: 'chain:audit {file} → no impacts detected ({duration}ms)',
36
+ results_header: 'chain:audit {file} → {count} impact(s) ({duration}ms):'
37
+ },
29
38
  context_load: {
30
39
  target_required: 'context:load requires --target=<rule|brain>:<slug>.',
31
40
  agent_required: 'context:load requires --agent=<name>.',
@@ -27,6 +27,15 @@ module.exports = {
27
27
  'aioson context:pack [path] [--agent=<agente>] [--goal=<texto>] [--module=<modulo-o-carpeta>] [--max-files=8] [--json] [--locale=es]',
28
28
  help_context_load:
29
29
  'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<nombre> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=es]',
30
+ help_chain_audit:
31
+ 'aioson chain:audit <archivo> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=es]',
32
+ chain_audit: {
33
+ file_required: 'chain:audit requiere una ruta de archivo. Uso: aioson chain:audit <archivo> [--limit=N] [--feature=<slug>] [--json]',
34
+ runtime_unavailable: 'chain:audit runtime db no disponible: {error}',
35
+ query_failed: 'chain:audit falló al consultar chain_edges: {error}',
36
+ no_impacts: 'chain:audit {file} → ningún impacto detectado ({duration}ms)',
37
+ results_header: 'chain:audit {file} → {count} impacto(s) ({duration}ms):'
38
+ },
30
39
  context_load: {
31
40
  target_required: 'context:load requiere --target=<rule|brain>:<slug>.',
32
41
  agent_required: 'context:load requiere --agent=<nombre>.',
@@ -27,6 +27,15 @@ module.exports = {
27
27
  'aioson context:pack [path] [--agent=<agent>] [--goal=<texte>] [--module=<module-ou-dossier>] [--max-files=8] [--json] [--locale=fr]',
28
28
  help_context_load:
29
29
  'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<nom> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=fr]',
30
+ help_chain_audit:
31
+ 'aioson chain:audit <fichier> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=fr]',
32
+ chain_audit: {
33
+ file_required: 'chain:audit exige un chemin de fichier. Usage : aioson chain:audit <fichier> [--limit=N] [--feature=<slug>] [--json]',
34
+ runtime_unavailable: 'chain:audit runtime db indisponible : {error}',
35
+ query_failed: 'chain:audit échec de la requête chain_edges : {error}',
36
+ no_impacts: 'chain:audit {file} → aucun impact détecté ({duration}ms)',
37
+ results_header: 'chain:audit {file} → {count} impact(s) ({duration}ms) :'
38
+ },
30
39
  context_load: {
31
40
  target_required: 'context:load exige --target=<rule|brain>:<slug>.',
32
41
  agent_required: 'context:load exige --agent=<nom>.',
@@ -27,6 +27,15 @@ module.exports = {
27
27
  'aioson context:pack [path] [--agent=<agente>] [--goal=<texto>] [--module=<modulo-ou-pasta>] [--max-files=8] [--json] [--locale=pt-BR]',
28
28
  help_context_load:
29
29
  'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<nome> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=pt-BR]',
30
+ help_chain_audit:
31
+ 'aioson chain:audit <arquivo> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=pt-BR]',
32
+ chain_audit: {
33
+ file_required: 'chain:audit exige um caminho de arquivo. Uso: aioson chain:audit <arquivo> [--limit=N] [--feature=<slug>] [--json]',
34
+ runtime_unavailable: 'chain:audit runtime db indisponível: {error}',
35
+ query_failed: 'chain:audit falhou ao consultar chain_edges: {error}',
36
+ no_impacts: 'chain:audit {file} → nenhum impacto detectado ({duration}ms)',
37
+ results_header: 'chain:audit {file} → {count} impacto(s) ({duration}ms):'
38
+ },
30
39
  context_load: {
31
40
  target_required: 'context:load exige --target=<rule|brain>:<slug>.',
32
41
  agent_required: 'context:load exige --agent=<nome>.',