feat(observer): node-dormancy extractor + initial JSON snapshot
Two-signal availability check: dormant=true OR boundaries contains DEFERRED. Treats #17 (Tooling-marked) and #44/#50/#54/#67 (DEFERRED in boundaries) uniformly as unavailable. Tooling Прил.Н unmodified — semantics preserved. 7 vitest cases (basic, multi-row, DEFERRED-fallback, boundary check). Initial JSON: 67 nodes, 6 unavailable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Brain Status (auto-generated)
|
||||
|
||||
Last updated: 2026-05-21T01:53:48.034Z
|
||||
Last updated: 2026-05-21T05:24:48.415Z
|
||||
|
||||
| Контролёр | Состояние | Детали |
|
||||
|---|---|---|
|
||||
@@ -8,12 +8,12 @@ Last updated: 2026-05-21T01:53:48.034Z
|
||||
| C2 Cross-ref consistency | ✅ | [cross-ref-checker] OK — 0 drift in 4 files |
|
||||
| C3 Observer-of-observer | ✅ | [observer-of-observer] OK — last read 0 week(s) ago |
|
||||
| C4 Сигнальный статус | ✅ | This file (self-reference) |
|
||||
| C5 Observer-coverage | ⚠️ | 16 episode(s) this month · .git/hooks/post-commit not installed (run: npx lefthook install --force) |
|
||||
| C5 Observer-coverage | ⚠️ | 39 episode(s) this month · .git/hooks/post-commit not installed (run: npx lefthook install --force) |
|
||||
| C6 Chain map sync | ✅ | [chain-map-checker] OK — 14 chains in sync |
|
||||
|
||||
## Метрики (информационные, не алерты)
|
||||
|
||||
- Observer evidence: 16 episodes this month, 0 observer_error markers, 0 PII matches before filter
|
||||
- Observer evidence: 39 episodes this month, 0 observer_error markers, 0 PII matches before filter
|
||||
- Legacy v1 episodes (not in factor analysis): 5
|
||||
- Last /brain-retro: 2 day(s) ago
|
||||
- Использование узлов: см. `/brain-retro` (раз в спринт). **Неиспользованные узлы — не проблема** (capability-readiness; см. memory `feedback_brain_unused_tools_not_problem` — outside-repo memory store).
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"#1": true,
|
||||
"#2": false,
|
||||
"#3": false,
|
||||
"#4": false,
|
||||
"#5": false,
|
||||
"#6": false,
|
||||
"#7": false,
|
||||
"#8": false,
|
||||
"#9": false,
|
||||
"#10": false,
|
||||
"#11": false,
|
||||
"#12": false,
|
||||
"#13": false,
|
||||
"#14": false,
|
||||
"#15": false,
|
||||
"#16": false,
|
||||
"#17": true,
|
||||
"#18": false,
|
||||
"#19": false,
|
||||
"#20": false,
|
||||
"#21": false,
|
||||
"#22": false,
|
||||
"#23": false,
|
||||
"#24": false,
|
||||
"#30": false,
|
||||
"#31": false,
|
||||
"#32": false,
|
||||
"#33": false,
|
||||
"#34": false,
|
||||
"#35": false,
|
||||
"#36": false,
|
||||
"#37": false,
|
||||
"#38": false,
|
||||
"#39": false,
|
||||
"#40": false,
|
||||
"#41": false,
|
||||
"#42": false,
|
||||
"#43": false,
|
||||
"#44": true,
|
||||
"#45": false,
|
||||
"#46": false,
|
||||
"#47": false,
|
||||
"#48": false,
|
||||
"#49": false,
|
||||
"#50": true,
|
||||
"#51": false,
|
||||
"#52": false,
|
||||
"#53": false,
|
||||
"#54": true,
|
||||
"#55": false,
|
||||
"#56": false,
|
||||
"#57": false,
|
||||
"#58": false,
|
||||
"#59": false,
|
||||
"#60": false,
|
||||
"#61": false,
|
||||
"#62": false,
|
||||
"#63": false,
|
||||
"#64": false,
|
||||
"#65": false,
|
||||
"#66": false,
|
||||
"#67": true,
|
||||
"#25": false,
|
||||
"#26": false,
|
||||
"#27": false,
|
||||
"#28": false,
|
||||
"#29": false
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Tooling Прил.Н dormancy extractor — emits {id: unavailable_bool} JSON for
|
||||
* the missed-activation matcher (Pravila §16.4 conditional rule).
|
||||
*
|
||||
* Two signals (either is sufficient) treat a node as effectively unavailable:
|
||||
* 1. `dormant: true` — Tooling-marked permanent dormancy (e.g. #17 pg_partman,
|
||||
* native Windows-PG cannot load the extension).
|
||||
* 2. `boundaries` column contains the word DEFERRED — node is registered
|
||||
* but not active (e.g. #44 Figma MCP "DEFERRED — нет Figma-аккаунта",
|
||||
* #50 Jupyter MCP, #54 n8n-mcp). The output key is still named "dormant"
|
||||
* for consumer simplicity — semantics: "node cannot be activated right
|
||||
* now, exclude from missed-activation counts".
|
||||
*
|
||||
* Parses 9-attribute table rows; ignores headers/separators/templates.
|
||||
*
|
||||
* Security Guidance #40: pure parsing — no exec/execSync.
|
||||
*/
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
|
||||
const ROW_RE = /^\|\s*#(\d+)\s*\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|([^|]+)\|\s*(true|false)\s*\|[^|]+\|$/gm;
|
||||
|
||||
export function extractDormancy(md) {
|
||||
const out = {};
|
||||
for (const m of md.matchAll(ROW_RE)) {
|
||||
const id = `#${m[1]}`;
|
||||
const boundaries = m[2];
|
||||
const tooledDormant = m[3] === 'true';
|
||||
out[id] = tooledDormant || /\bDEFERRED\b/.test(boundaries);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
if (process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/extract-node-dormancy.mjs')) {
|
||||
const src = readFileSync('docs/Tooling_v8_3.md', 'utf-8');
|
||||
const dormancy = extractDormancy(src);
|
||||
writeFileSync('tools/.node-dormancy.json', JSON.stringify(dormancy, null, 2) + '\n');
|
||||
console.log(`[extract-node-dormancy] OK — ${Object.keys(dormancy).length} nodes`);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { extractDormancy } from './extract-node-dormancy.mjs';
|
||||
|
||||
describe('extractDormancy', () => {
|
||||
it('returns false for a live row (dormant=false, no DEFERRED in boundaries)', () => {
|
||||
const md = [
|
||||
'#### #10 Laravel Boost',
|
||||
'',
|
||||
'**Атрибуты:**',
|
||||
'',
|
||||
'| id | name | kind | phase | subcategory | triggers | boundaries | dormant | last-touched |',
|
||||
'|---|---|---|---|---|---|---|---|---|',
|
||||
'| #10 | Laravel Boost | composer-dep | 1 | — | «SQL, Eloquent» | replaces #1 PG MCP | false | 2026-05-19 |',
|
||||
].join('\n');
|
||||
expect(extractDormancy(md)).toEqual({ '#10': false });
|
||||
});
|
||||
|
||||
it('returns true when Tooling marks dormant=true', () => {
|
||||
const md = '| #17 | pg_partman | binary-dep | 1 | — | «partition mgmt» | none | true | 2026-05-19 |';
|
||||
expect(extractDormancy(md)).toEqual({ '#17': true });
|
||||
});
|
||||
|
||||
it('returns true when boundaries contains DEFERRED (even if dormant=false)', () => {
|
||||
const md = '| #44 | Figma MCP | mcp | off-phase | design-tooling | «figma extract» | DEFERRED — нет Figma-аккаунта | false | 2026-05-19 |';
|
||||
expect(extractDormancy(md)).toEqual({ '#44': true });
|
||||
});
|
||||
|
||||
it('handles multiple nodes in one pass (mixed signals)', () => {
|
||||
const md = [
|
||||
'| #44 | Figma MCP | mcp | off-phase | design-tooling | «figma extract» | DEFERRED — нет Figma | false | 2026-05-17 |',
|
||||
'| #45 | Universal Icons MCP | mcp | off-phase | design-tooling | «svg search» | non-Lucide | false | 2026-05-17 |',
|
||||
].join('\n');
|
||||
expect(extractDormancy(md)).toEqual({ '#44': true, '#45': false });
|
||||
});
|
||||
|
||||
it('ignores header/separator rows', () => {
|
||||
const md = [
|
||||
'| id | name | kind | phase | subcategory | triggers | boundaries | dormant | last-touched |',
|
||||
'|---|---|---|---|---|---|---|---|---|',
|
||||
].join('\n');
|
||||
expect(extractDormancy(md)).toEqual({});
|
||||
});
|
||||
|
||||
it('ignores non-numeric ids (template placeholders)', () => {
|
||||
const md = '| #NN | <name> | <kind> | <phase> | <subcat or —> | «<triggers>» | <ADR-NNN or none> | false | 2026-05-19 |';
|
||||
expect(extractDormancy(md)).toEqual({});
|
||||
});
|
||||
|
||||
it('does NOT match the word DEFERRED inside a longer token (boundary check)', () => {
|
||||
const md = '| #99 | fake | mcp | off | tooling | «t» | NODEFERREDX prefix | false | 2026-05-19 |';
|
||||
expect(extractDormancy(md)).toEqual({ '#99': false });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user