@rahmantech/knl-view 0.1.0 → 0.3.0
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/README.md +133 -32
- package/bin/knl-view.js +591 -58
- package/dist/assets/{abnfDiagram-WEYOIA3C-B1gaBaSI.js → abnfDiagram-WEYOIA3C-hoSbsMOz.js} +1 -1
- package/dist/assets/{arc-NZ9uRaCl.js → arc-g87Np6E5.js} +1 -1
- package/dist/assets/{architecture-WOLXFQ4H-8MalLwTd.js → architecture-WOLXFQ4H-DOIq-0IA.js} +1 -1
- package/dist/assets/{architectureDiagram-ZHVYKT53-eZ8i5cl4.js → architectureDiagram-ZHVYKT53-Dxwe8n7m.js} +1 -1
- package/dist/assets/{blockDiagram-RJAE7QA3-BavOE8BM.js → blockDiagram-RJAE7QA3-BnGng59Z.js} +1 -1
- package/dist/assets/{c4Diagram-XDPPPE54-DqMaTAzx.js → c4Diagram-XDPPPE54-C5LjqIIo.js} +1 -1
- package/dist/assets/channel-BVWkRFBw.js +1 -0
- package/dist/assets/{chunk-2E7WHVMD-B1Hj2BjJ.js → chunk-2E7WHVMD-BJ1dyGLq.js} +1 -1
- package/dist/assets/{chunk-4EMNKR2T-HDscrCiq.js → chunk-4EMNKR2T-DXF02Sgw.js} +1 -1
- package/dist/assets/{chunk-55XCU76K-QR_ARj8I.js → chunk-55XCU76K-CM1akAbu.js} +1 -1
- package/dist/assets/{chunk-7S6XSTFO-B0p3o547.js → chunk-7S6XSTFO-BOUjqldL.js} +1 -1
- package/dist/assets/{chunk-AQDTSKZB-CCxwNyyA.js → chunk-AQDTSKZB-C4isdNLA.js} +1 -1
- package/dist/assets/{chunk-DSNLSHJZ-BM5PBFmz.js → chunk-DSNLSHJZ-CbOTkSVM.js} +1 -1
- package/dist/assets/{chunk-HAGFOTED-UCDVFuN_.js → chunk-HAGFOTED-CRfnketR.js} +1 -1
- package/dist/assets/{chunk-HNE4Q36P-C_1sakp2.js → chunk-HNE4Q36P-rxlpGUU8.js} +1 -1
- package/dist/assets/{chunk-NQOKRVIF-b2D1Z4oK.js → chunk-NQOKRVIF-BOYCgTmV.js} +1 -1
- package/dist/assets/{chunk-UHW7PFBP-CJs0VwBY.js → chunk-UHW7PFBP-Cnogp1Fb.js} +1 -1
- package/dist/assets/{chunk-VQ5W62QI-DQxjgE4_.js → chunk-VQ5W62QI-Cu1WY5Uo.js} +1 -1
- package/dist/assets/{chunk-WQQL4CDK-DcjPeAfQ.js → chunk-WQQL4CDK-BJc3FPE2.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-glA0kB5s.js → chunk-XXDRQBXY-BlPj4H7s.js} +1 -1
- package/dist/assets/{chunk-YZO67HFG-DJqACL5V.js → chunk-YZO67HFG-V85H31x4.js} +1 -1
- package/dist/assets/{chunk-ZAUX2LTX-CWfaYtwb.js → chunk-ZAUX2LTX-C3XKXW09.js} +1 -1
- package/dist/assets/{chunk-ZCHMQSOR-BGX73Hho.js → chunk-ZCHMQSOR-B0mmpEgW.js} +1 -1
- package/dist/assets/{classDiagram-v2-4MYWR3UA-BpYXPz-e.js → classDiagram-v2-4MYWR3UA-oMd7YaHP.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-e1quKm0Q.js → cose-bilkent-JH36ORCC-J0kSVsDf.js} +1 -1
- package/dist/assets/{cynefin-EF2NZ3EQ-hb3oUmFO.js → cynefin-EF2NZ3EQ-DK2TNhel.js} +1 -1
- package/dist/assets/{cynefinDiagram-WF24UDRP-CnMQvLqi.js → cynefinDiagram-WF24UDRP-DDvKgieA.js} +1 -1
- package/dist/assets/{dagre-NK4GIZVX-BH_Y8y3F.js → dagre-NK4GIZVX-Ca2Fe-i-.js} +1 -1
- package/dist/assets/{diagram-CBK3MWTC-Dh79VetZ.js → diagram-CBK3MWTC-L6dmeQ2V.js} +1 -1
- package/dist/assets/{diagram-I5XIIV2D-D3u-o1RV.js → diagram-I5XIIV2D-DhG8R-v5.js} +1 -1
- package/dist/assets/{diagram-ODIL2UCW-Bfuo-OKS.js → diagram-ODIL2UCW-BpYYbYBT.js} +1 -1
- package/dist/assets/{diagram-PFBEJUXE-CwHasIbs.js → diagram-PFBEJUXE-DKQpHSsC.js} +1 -1
- package/dist/assets/{diagram-RJA3I4VV-D8ttmuE8.js → diagram-RJA3I4VV-DP7Dsnya.js} +1 -1
- package/dist/assets/{diagram-TYUNFSNR-CzC-8RZ0.js → diagram-TYUNFSNR-CfSFewUS.js} +1 -1
- package/dist/assets/{dist-CUJYGnnI.js → dist-CQNjnrld.js} +1 -1
- package/dist/assets/{ebnfDiagram-V4P3TTPT-9AAhU7In.js → ebnfDiagram-V4P3TTPT-DE3MZoJO.js} +1 -1
- package/dist/assets/{elk-QONTBCTQ-BgvnD1v_.js → elk-QONTBCTQ-p_aAQPHU.js} +1 -1
- package/dist/assets/{erDiagram-KYQIGQAA-RLtbiLlP.js → erDiagram-KYQIGQAA-DZO3vQrk.js} +1 -1
- package/dist/assets/{eventmodeling-K75KTNOO-DVY22vNZ.js → eventmodeling-K75KTNOO-BII9EPl5.js} +1 -1
- package/dist/assets/flowDiagram-IUGLLWBU-D9uPno39.js +1 -0
- package/dist/assets/{ganttDiagram-R7GPNTJY-hHiBGa9W.js → ganttDiagram-R7GPNTJY-BKiH3v2r.js} +1 -1
- package/dist/assets/{gitGraph-VSP46ZUC-Cvxay1wh.js → gitGraph-VSP46ZUC-oHL6cr73.js} +1 -1
- package/dist/assets/{gitGraphDiagram-PDYL7XHY-AOhlpSqg.js → gitGraphDiagram-PDYL7XHY-CqPVIEeo.js} +1 -1
- package/dist/assets/{index-CBLNQvK3.js → index-CSpirV16.js} +3 -3
- package/dist/assets/{info-OHQRW6UA-CJ8EUO1m.js → info-OHQRW6UA-Drrbnhpd.js} +1 -1
- package/dist/assets/{infoDiagram-MVDRKAX5-DOQu7oYI.js → infoDiagram-MVDRKAX5-DnQQFMAd.js} +1 -1
- package/dist/assets/{ishikawaDiagram-HPEHV2GE-DcVMvGmx.js → ishikawaDiagram-HPEHV2GE-DIpQlxRh.js} +1 -1
- package/dist/assets/{journeyDiagram-BOSHW7O7-luMeqEqz.js → journeyDiagram-BOSHW7O7-EBR9I5h1.js} +1 -1
- package/dist/assets/{kanban-definition-FNSA4HC5-KTZbmrW2.js → kanban-definition-FNSA4HC5-CdvNsjik.js} +1 -1
- package/dist/assets/{line-CmPHiOVm.js → line-CDLzgxDc.js} +1 -1
- package/dist/assets/{linear-DHYlKXBA.js → linear-DfXKql9u.js} +1 -1
- package/dist/assets/{mermaid-parser.core-BRDI-WhC.js → mermaid-parser.core-DnXGphT2.js} +3 -3
- package/dist/assets/{mermaid.core-DPTRaYLy.js → mermaid.core-h6fiCaSZ.js} +4 -4
- package/dist/assets/{mindmap-definition-6OY57UG3-70EN5Kw_.js → mindmap-definition-6OY57UG3-BiNfio-j.js} +1 -1
- package/dist/assets/{packet-JDAUHWVQ-CXWz83oS.js → packet-JDAUHWVQ-D76byv1S.js} +1 -1
- package/dist/assets/{pegDiagram-ECIUJNBX-z5UXKmp4.js → pegDiagram-ECIUJNBX-Drs8Pxdt.js} +1 -1
- package/dist/assets/{pie-XZMESJXO-rWI1HIQ7.js → pie-XZMESJXO-D_83z1lM.js} +1 -1
- package/dist/assets/{pieDiagram-GNMPY74T-Bh4Kv3NQ.js → pieDiagram-GNMPY74T-wvzIphSf.js} +1 -1
- package/dist/assets/{quadrantDiagram-D57K4MC3-wGM4Ckig.js → quadrantDiagram-D57K4MC3-B7EfMRWb.js} +1 -1
- package/dist/assets/{radar-ABXABTNO-CIJ92p5N.js → radar-ABXABTNO-Bs6dVdAR.js} +1 -1
- package/dist/assets/{railroad-I3PHUGI6-8bxua-Hf.js → railroad-I3PHUGI6-lKaN4miw.js} +1 -1
- package/dist/assets/{railroad-abnf-LNEFI6M7-B9KVI10m.js → railroad-abnf-LNEFI6M7-Bhh16b7Z.js} +1 -1
- package/dist/assets/{railroad-ebnf-DWYD2UWJ-Bt73rnyJ.js → railroad-ebnf-DWYD2UWJ-m6ybW-Y_.js} +1 -1
- package/dist/assets/{railroad-peg-JR7BVNR7-Cie82UPT.js → railroad-peg-JR7BVNR7-DNn72e7r.js} +1 -1
- package/dist/assets/{railroadDiagram-Y72NRBL7-C1nmi_tl.js → railroadDiagram-Y72NRBL7-DNeop1iZ.js} +1 -1
- package/dist/assets/{requirementDiagram-EQYSW5RA-BSianW40.js → requirementDiagram-EQYSW5RA-BVYZ2XYJ.js} +1 -1
- package/dist/assets/{sankeyDiagram-SY24JHO5-DIa11-h7.js → sankeyDiagram-SY24JHO5-B2MP51on.js} +1 -1
- package/dist/assets/{sequenceDiagram-BLEHNHQU-Df5rZKz2.js → sequenceDiagram-BLEHNHQU-BUvxstXG.js} +1 -1
- package/dist/assets/{src-dClg85lX.js → src-BrR0lqlG.js} +1 -1
- package/dist/assets/{stateDiagram-v2-A6IESKWZ-B15PTSvX.js → stateDiagram-v2-A6IESKWZ-Ba84OGKE.js} +1 -1
- package/dist/assets/{swimlanes-B3ENRKFQ-C9mgRCMi.js → swimlanes-B3ENRKFQ-BMSl2PNA.js} +1 -1
- package/dist/assets/swimlanesDiagram-AYQRV4AK-M1dmvq7m.js +8 -0
- package/dist/assets/{timeline-definition-JXYKGLGB-BDVf_mIv.js → timeline-definition-JXYKGLGB-DOdW-dxq.js} +1 -1
- package/dist/assets/{treeView-D4JQ5SDB-BbBK-25J.js → treeView-D4JQ5SDB-DZ9KQyn5.js} +1 -1
- package/dist/assets/{treemap-SAJKECNS-CEDX8apk.js → treemap-SAJKECNS-WEC5wtDV.js} +1 -1
- package/dist/assets/{usecaseDiagram-WPOLBNNX-D1QkwFF8.js → usecaseDiagram-WPOLBNNX-cldTztFL.js} +1 -1
- package/dist/assets/{vennDiagram-Q3Y3IDCW-5PeGZvEO.js → vennDiagram-Q3Y3IDCW-c41KySoY.js} +1 -1
- package/dist/assets/{wardley-7MLQ67FV-BatN94pO.js → wardley-7MLQ67FV-Bf9_aSSZ.js} +1 -1
- package/dist/assets/{wardleyDiagram-IVAIDKWA-DyCcDS3Y.js → wardleyDiagram-IVAIDKWA-BeKEvEBG.js} +1 -1
- package/dist/assets/{xychartDiagram-4URNHDU4-CbCXcRlg.js → xychartDiagram-4URNHDU4-tNkAxTxG.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +5 -1
- package/server/config.js +140 -14
- package/server/consensus.js +18 -13
- package/server/graph/modules_builder.js +290 -0
- package/server/graph/modules_catalog.js +288 -0
- package/server/graph/parser.js +3 -2
- package/server/graph/sources.js +165 -24
- package/server/graph/syntax.js +245 -28
- package/server/index.js +124 -61
- package/server/install.js +350 -10
- package/server/models_registry.js +34 -9
- package/server/scaffold.js +109 -9
- package/dist/assets/channel-BZ2_TTTJ.js +0 -1
- package/dist/assets/flowDiagram-IUGLLWBU-l7L5DTb5.js +0 -1
- package/dist/assets/swimlanesDiagram-AYQRV4AK-BGFq4t7O.js +0 -8
package/server/install.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import crypto from 'node:crypto'
|
|
4
|
+
import net from 'node:net'
|
|
5
|
+
import { globalConfigPath } from './config.js'
|
|
4
6
|
|
|
5
7
|
// AnchorDB installer for knl-view. Two paths:
|
|
6
8
|
// - Linux: binary from GitHub Releases (tar.gz per architecture, verified
|
|
@@ -11,10 +13,64 @@ import crypto from 'node:crypto'
|
|
|
11
13
|
// orchestration (prompts, spawn) stays in bin/knl-view.js.
|
|
12
14
|
|
|
13
15
|
export const GHCR_IMAGE = 'ghcr.io/rachmanzz/anchordb'
|
|
14
|
-
export const HTTP_PORT_DEFAULT =
|
|
15
|
-
export const WIRE_PORT_DEFAULT =
|
|
16
|
+
export const HTTP_PORT_DEFAULT = 17447
|
|
17
|
+
export const WIRE_PORT_DEFAULT = 17448
|
|
16
18
|
export const CONTAINER_NAME = 'anchordb-knlview'
|
|
17
19
|
|
|
20
|
+
/** Check if a TCP port is currently free on a host. */
|
|
21
|
+
export async function isPortAvailable(port, host = '127.0.0.1') {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
const server = net.createServer()
|
|
24
|
+
server.once('error', () => resolve(false))
|
|
25
|
+
server.once('listening', () => {
|
|
26
|
+
server.close(() => resolve(true))
|
|
27
|
+
})
|
|
28
|
+
server.listen(port, host)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Find the next available TCP port sequentially starting from startPort. */
|
|
33
|
+
export async function findNextAvailablePort(startPort, host = '127.0.0.1') {
|
|
34
|
+
let port = Number(startPort)
|
|
35
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
36
|
+
throw new RangeError(`Invalid start port: ${startPort}`)
|
|
37
|
+
}
|
|
38
|
+
while (!(await isPortAvailable(port, host))) {
|
|
39
|
+
port++
|
|
40
|
+
if (port > 65535) {
|
|
41
|
+
throw new Error(`No available port found up to 65535 starting from ${startPort}`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return port
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Check desired AnchorDB ports and suggest alternatives if any is taken. */
|
|
48
|
+
export async function resolveAnchorPorts({
|
|
49
|
+
desiredWirePort = WIRE_PORT_DEFAULT,
|
|
50
|
+
desiredHttpPort = HTTP_PORT_DEFAULT,
|
|
51
|
+
host = '127.0.0.1'
|
|
52
|
+
} = {}) {
|
|
53
|
+
const wireAvailable = await isPortAvailable(desiredWirePort, host)
|
|
54
|
+
const httpAvailable = desiredHttpPort !== desiredWirePort && (await isPortAvailable(desiredHttpPort, host))
|
|
55
|
+
|
|
56
|
+
const suggestedWirePort = wireAvailable ? desiredWirePort : await findNextAvailablePort(desiredWirePort + 1, host)
|
|
57
|
+
let candidateHttp = (httpAvailable && desiredHttpPort !== suggestedWirePort)
|
|
58
|
+
? desiredHttpPort
|
|
59
|
+
: await findNextAvailablePort(desiredHttpPort + 1, host)
|
|
60
|
+
if (candidateHttp === suggestedWirePort) {
|
|
61
|
+
candidateHttp = await findNextAvailablePort(candidateHttp + 1, host)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
wirePort: desiredWirePort,
|
|
66
|
+
httpPort: desiredHttpPort,
|
|
67
|
+
wireAvailable,
|
|
68
|
+
httpAvailable,
|
|
69
|
+
suggestedWirePort,
|
|
70
|
+
suggestedHttpPort: candidateHttp
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
18
74
|
/** Release asset name for a platform/architecture, per the AnchorDB release pattern. */
|
|
19
75
|
export function releaseAssetName(version, platform = process.platform, arch = process.arch) {
|
|
20
76
|
const archName = arch === 'arm64' ? 'arm64' : 'amd64'
|
|
@@ -66,12 +122,12 @@ export async function verifySha256(filePath, sumsText) {
|
|
|
66
122
|
}
|
|
67
123
|
|
|
68
124
|
/**
|
|
69
|
-
* Merge the anchordb block into the global config (
|
|
125
|
+
* Merge the anchordb block into the global config (knl-config.json)
|
|
70
126
|
* without dropping other fields that may live there. Returns the config
|
|
71
127
|
* contents before the overwrite (as a backup, in case the caller shows it).
|
|
72
128
|
*/
|
|
73
|
-
export async function mergeGlobalAnchordb(home, anchordb) {
|
|
74
|
-
const file =
|
|
129
|
+
export async function mergeGlobalAnchordb(home, anchordb, options = {}) {
|
|
130
|
+
const file = globalConfigPath(home, options)
|
|
75
131
|
let before = null
|
|
76
132
|
let raw = {}
|
|
77
133
|
try {
|
|
@@ -83,15 +139,299 @@ export async function mergeGlobalAnchordb(home, anchordb) {
|
|
|
83
139
|
// Missing/broken file: start from an empty object.
|
|
84
140
|
}
|
|
85
141
|
raw.anchordb = anchordb
|
|
86
|
-
await fs.mkdir(path.dirname(file), { recursive: true })
|
|
87
|
-
await fs.writeFile(file, JSON.stringify(raw, null, 2) + '\n', 'utf8')
|
|
142
|
+
await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 })
|
|
143
|
+
await fs.writeFile(file, JSON.stringify(raw, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 })
|
|
144
|
+
try { await fs.chmod(file, 0o600) } catch {}
|
|
88
145
|
return before
|
|
89
146
|
}
|
|
90
147
|
|
|
91
|
-
/**
|
|
92
|
-
export async function
|
|
148
|
+
/** List all available container runtimes on the system. */
|
|
149
|
+
export async function availableContainerRuntimes(which) {
|
|
150
|
+
const found = []
|
|
151
|
+
for (const runtime of ['docker', 'podman']) {
|
|
152
|
+
if (await which(runtime)) found.push(runtime)
|
|
153
|
+
}
|
|
154
|
+
return found
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Pick an available container runtime: preferred first, or first detected. */
|
|
158
|
+
export async function detectContainerRuntime(which, preferred = null) {
|
|
159
|
+
if (preferred) {
|
|
160
|
+
if (await which(preferred)) return preferred
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
const available = await availableContainerRuntimes(which)
|
|
164
|
+
return available[0] || null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Inspect detailed container lifecycle status across available runtimes.
|
|
169
|
+
* Returns exists, runtime, running, valid, status, id, created, token, wire_port.
|
|
170
|
+
*/
|
|
171
|
+
export async function inspectContainerStatus(exec, name = CONTAINER_NAME) {
|
|
93
172
|
for (const runtime of ['podman', 'docker']) {
|
|
94
|
-
|
|
173
|
+
try {
|
|
174
|
+
const { stdout } = await exec(runtime, [
|
|
175
|
+
'inspect',
|
|
176
|
+
'--format',
|
|
177
|
+
'{{.Id}}|{{.Created}}|{{.State.Status}}|{{.State.Running}}|{{json .Config.Env}}',
|
|
178
|
+
name
|
|
179
|
+
])
|
|
180
|
+
const parts = stdout.trim().split('|')
|
|
181
|
+
if (parts.length >= 4 && parts[0]) {
|
|
182
|
+
const id = parts[0].trim()
|
|
183
|
+
const created = parts[1].trim()
|
|
184
|
+
const status = (parts[2] || '').trim().toLowerCase()
|
|
185
|
+
const running = (parts[3] || '').trim().toLowerCase() === 'true'
|
|
186
|
+
const valid = !['dead', 'removal', 'corrupt'].includes(status) && id.length >= 12
|
|
187
|
+
const envStr = parts.slice(4).join('|')
|
|
188
|
+
let token = null
|
|
189
|
+
let port = String(WIRE_PORT_DEFAULT)
|
|
190
|
+
try {
|
|
191
|
+
const envs = JSON.parse(envStr)
|
|
192
|
+
for (const e of envs) {
|
|
193
|
+
if (e.startsWith('ANCHORDB_AUTH_TOKEN=')) token = e.slice('ANCHORDB_AUTH_TOKEN='.length)
|
|
194
|
+
if (e.startsWith('ANCHORDB_WIRE_PORT=')) port = e.slice('ANCHORDB_WIRE_PORT='.length)
|
|
195
|
+
}
|
|
196
|
+
} catch {}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
exists: true,
|
|
200
|
+
runtime,
|
|
201
|
+
name,
|
|
202
|
+
id,
|
|
203
|
+
created,
|
|
204
|
+
status,
|
|
205
|
+
running,
|
|
206
|
+
valid,
|
|
207
|
+
token,
|
|
208
|
+
wire_port: port
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// not in this runtime
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
exists: false,
|
|
218
|
+
runtime: null,
|
|
219
|
+
name,
|
|
220
|
+
id: null,
|
|
221
|
+
created: null,
|
|
222
|
+
status: null,
|
|
223
|
+
running: false,
|
|
224
|
+
valid: false,
|
|
225
|
+
token: null,
|
|
226
|
+
wire_port: String(WIRE_PORT_DEFAULT)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function startContainer(exec, runtime, name = CONTAINER_NAME) {
|
|
231
|
+
await exec(runtime, ['start', name])
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function removeContainer(exec, runtime, name = CONTAINER_NAME) {
|
|
235
|
+
await exec(runtime, ['rm', '-f', name])
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function detectGraphify(exec) {
|
|
239
|
+
try {
|
|
240
|
+
await exec('which', ['graphify'])
|
|
241
|
+
return true
|
|
242
|
+
} catch {
|
|
243
|
+
return false
|
|
95
244
|
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const DECISIONSKILLS_REPO = 'rachmanzz/decisionskills'
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if the decision-history skill (from rachmanzz/decisionskills) is
|
|
251
|
+
* installed in project or global agent directories, and if an update is available.
|
|
252
|
+
*/
|
|
253
|
+
export async function checkDecisionSkillsStatus({ cwd = process.cwd(), home = process.env.HOME }) {
|
|
254
|
+
const projectSkill = path.join(cwd, '.agents', 'skills', 'decision-history', 'SKILL.md')
|
|
255
|
+
let hasProject = false
|
|
256
|
+
try {
|
|
257
|
+
await fs.stat(projectSkill)
|
|
258
|
+
hasProject = true
|
|
259
|
+
} catch {}
|
|
260
|
+
|
|
261
|
+
const globalPaths = [
|
|
262
|
+
path.join(home || '', '.codex', 'skills', 'decision-history', 'SKILL.md'),
|
|
263
|
+
path.join(home || '', '.claude', 'skills', 'decision-history', 'SKILL.md'),
|
|
264
|
+
path.join(home || '', '.config', 'opencode', 'skills', 'decision-history', 'SKILL.md'),
|
|
265
|
+
path.join(home || '', '.gemini', 'config', 'plugins', 'decision-history', 'skills', 'decision-history', 'SKILL.md'),
|
|
266
|
+
path.join(home || '', '.agents', 'skills', 'decision-history', 'SKILL.md')
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
let hasGlobal = false
|
|
270
|
+
for (const p of globalPaths) {
|
|
271
|
+
try {
|
|
272
|
+
await fs.stat(p)
|
|
273
|
+
hasGlobal = true
|
|
274
|
+
break
|
|
275
|
+
} catch {}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let currentVersion = null
|
|
279
|
+
let latestVersion = null
|
|
280
|
+
let status = 'not_installed'
|
|
281
|
+
|
|
282
|
+
if (hasProject || hasGlobal) {
|
|
283
|
+
status = 'installed'
|
|
284
|
+
const versionCandidates = [
|
|
285
|
+
path.join(cwd, '.agents', 'skills', 'decision-history', 'VERSION'),
|
|
286
|
+
path.join(home || '', '.gemini', 'config', 'plugins', 'decision-history', 'VERSION')
|
|
287
|
+
]
|
|
288
|
+
for (const vFile of versionCandidates) {
|
|
289
|
+
try {
|
|
290
|
+
const text = await fs.readFile(vFile, 'utf8')
|
|
291
|
+
if (text.trim()) {
|
|
292
|
+
currentVersion = text.trim()
|
|
293
|
+
break
|
|
294
|
+
}
|
|
295
|
+
} catch {}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const res = await fetch('https://raw.githubusercontent.com/rachmanzz/decisionskills/main/VERSION', {
|
|
300
|
+
signal: AbortSignal.timeout(2000)
|
|
301
|
+
})
|
|
302
|
+
if (res.ok) {
|
|
303
|
+
latestVersion = (await res.text()).trim()
|
|
304
|
+
if (currentVersion && latestVersion && currentVersion !== latestVersion) {
|
|
305
|
+
status = 'needs_update'
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} catch {}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
hasProject,
|
|
313
|
+
hasGlobal,
|
|
314
|
+
status, // 'not_installed' | 'installed' | 'needs_update'
|
|
315
|
+
currentVersion,
|
|
316
|
+
latestVersion,
|
|
317
|
+
projectPath: projectSkill
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Install or update the decision-history skill from rachmanzz/decisionskills.
|
|
323
|
+
* Scope: 'project' | 'global' | 'both'.
|
|
324
|
+
*/
|
|
325
|
+
export async function installDecisionSkills({
|
|
326
|
+
scope = 'project',
|
|
327
|
+
cwd = process.cwd(),
|
|
328
|
+
home = process.env.HOME,
|
|
329
|
+
exec
|
|
330
|
+
}) {
|
|
331
|
+
const results = []
|
|
332
|
+
|
|
333
|
+
const copyDir = async (src, dest) => {
|
|
334
|
+
await fs.mkdir(dest, { recursive: true })
|
|
335
|
+
const entries = await fs.readdir(src, { withFileTypes: true })
|
|
336
|
+
for (const entry of entries) {
|
|
337
|
+
const srcPath = path.join(src, entry.name)
|
|
338
|
+
const destPath = path.join(dest, entry.name)
|
|
339
|
+
if (entry.isDirectory()) {
|
|
340
|
+
await copyDir(srcPath, destPath)
|
|
341
|
+
} else {
|
|
342
|
+
await fs.copyFile(srcPath, destPath)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const doInstall = async (isGlobal) => {
|
|
348
|
+
let success = false
|
|
349
|
+
if (exec) {
|
|
350
|
+
try {
|
|
351
|
+
const args = ['--yes', 'skills', 'add', DECISIONSKILLS_REPO, '--skill', 'decision-history', '-y', '--copy']
|
|
352
|
+
if (isGlobal) args.push('-g')
|
|
353
|
+
await exec('npx', args, { cwd })
|
|
354
|
+
success = true
|
|
355
|
+
} catch {}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (!success) {
|
|
359
|
+
const fallbackSources = [
|
|
360
|
+
path.join(home || '', '.gemini', 'config', 'plugins', 'decision-history', 'skills', 'decision-history'),
|
|
361
|
+
path.join(home || '', '.agents', 'skills', 'decision-history'),
|
|
362
|
+
path.join(home || '', '.codex', 'skills', 'decision-history')
|
|
363
|
+
]
|
|
364
|
+
let srcDir = null
|
|
365
|
+
for (const candidate of fallbackSources) {
|
|
366
|
+
try {
|
|
367
|
+
await fs.stat(path.join(candidate, 'SKILL.md'))
|
|
368
|
+
srcDir = candidate
|
|
369
|
+
break
|
|
370
|
+
} catch {}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (srcDir) {
|
|
374
|
+
const dest = isGlobal
|
|
375
|
+
? path.join(home || '', '.agents', 'skills', 'decision-history')
|
|
376
|
+
: path.join(cwd, '.agents', 'skills', 'decision-history')
|
|
377
|
+
await copyDir(srcDir, dest)
|
|
378
|
+
success = true
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (isGlobal && exec) {
|
|
383
|
+
const agyDir = path.join(home || '', '.gemini', 'config', 'plugins', 'decision-history')
|
|
384
|
+
try {
|
|
385
|
+
await fs.stat(agyDir)
|
|
386
|
+
await exec('git', ['pull'], { cwd: agyDir })
|
|
387
|
+
} catch {
|
|
388
|
+
try {
|
|
389
|
+
await fs.mkdir(path.dirname(agyDir), { recursive: true })
|
|
390
|
+
await exec('git', ['clone', 'https://github.com/rachmanzz/decisionskills.git', agyDir])
|
|
391
|
+
} catch {}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return success
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (scope === 'project' || scope === 'both') {
|
|
399
|
+
const ok = await doInstall(false)
|
|
400
|
+
results.push({ scope: 'project', success: ok })
|
|
401
|
+
}
|
|
402
|
+
if (scope === 'global' || scope === 'both') {
|
|
403
|
+
const ok = await doInstall(true)
|
|
404
|
+
results.push({ scope: 'global', success: ok })
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return results
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Detect a running AnchorDB daemon on the system (docker, podman, or binary).
|
|
412
|
+
* Inspects containers for CONTAINER_NAME or checks running processes.
|
|
413
|
+
*/
|
|
414
|
+
export async function detectRunningDaemon(exec) {
|
|
415
|
+
const container = await inspectContainerStatus(exec)
|
|
416
|
+
if (container.exists && container.running && container.valid) {
|
|
417
|
+
return {
|
|
418
|
+
daemon: container.runtime,
|
|
419
|
+
deamon: container.runtime,
|
|
420
|
+
container_name: container.name,
|
|
421
|
+
container_id: container.id,
|
|
422
|
+
container_created_on: container.created,
|
|
423
|
+
token: container.token,
|
|
424
|
+
wire_port: container.wire_port
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Check if binary process is running
|
|
429
|
+
try {
|
|
430
|
+
const { stdout } = await exec('pgrep', ['-f', 'anchordb-server'])
|
|
431
|
+
if (stdout.trim()) {
|
|
432
|
+
return { daemon: 'binary', deamon: 'binary' }
|
|
433
|
+
}
|
|
434
|
+
} catch {}
|
|
435
|
+
|
|
96
436
|
return null
|
|
97
437
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import crypto from 'node:crypto'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import { knlGlobalDir } from './config.js'
|
|
4
6
|
|
|
5
|
-
// Global registry of AnchorDB model names:
|
|
7
|
+
// Global registry of AnchorDB model names:
|
|
8
|
+
// - %APPDATA%\knlview\models.json on Windows
|
|
9
|
+
// - ~/.knlview/models.json on Linux and macOS
|
|
6
10
|
//
|
|
7
11
|
// Model names may be random; what makes a random name meaningful is this
|
|
8
12
|
// registry: the mapping of name → project, role, and project location.
|
|
@@ -11,8 +15,8 @@ import crypto from 'node:crypto'
|
|
|
11
15
|
|
|
12
16
|
const REGISTRY_FILE = 'models.json'
|
|
13
17
|
|
|
14
|
-
export function modelRegistryPath(home =
|
|
15
|
-
return path.join(home
|
|
18
|
+
export function modelRegistryPath(home = null, options = {}) {
|
|
19
|
+
return path.join(knlGlobalDir(home, options), REGISTRY_FILE)
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/**
|
|
@@ -31,9 +35,31 @@ export function generateModelName() {
|
|
|
31
35
|
* Read the registry. Missing/broken file = empty registry, not an error.
|
|
32
36
|
* @returns {Promise<{models: object, error: string|null}>}
|
|
33
37
|
*/
|
|
34
|
-
export async function readModelRegistry(home =
|
|
38
|
+
export async function readModelRegistry(home = null, options = {}) {
|
|
39
|
+
const primary = modelRegistryPath(home, options)
|
|
40
|
+
let content = null
|
|
35
41
|
try {
|
|
36
|
-
|
|
42
|
+
content = await fs.readFile(primary, 'utf8')
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (err?.code === 'ENOENT') {
|
|
45
|
+
const platform = options.platform || process.platform
|
|
46
|
+
const env = options.env || process.env
|
|
47
|
+
if (platform === 'win32' && !home) {
|
|
48
|
+
const winHome = env.USERPROFILE || env.HOME || (typeof os.homedir === 'function' ? os.homedir() : null)
|
|
49
|
+
if (winHome) {
|
|
50
|
+
try {
|
|
51
|
+
content = await fs.readFile(path.join(winHome, '.knlview', REGISTRY_FILE), 'utf8')
|
|
52
|
+
} catch {}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!content) return { models: {}, error: null }
|
|
56
|
+
} else {
|
|
57
|
+
return { models: {}, error: err.message }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(content)
|
|
37
63
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
38
64
|
return { models: {}, error: 'registry contents are not an object' }
|
|
39
65
|
}
|
|
@@ -45,7 +71,6 @@ export async function readModelRegistry(home = process.env.HOME) {
|
|
|
45
71
|
}
|
|
46
72
|
return { models, error: null }
|
|
47
73
|
} catch (err) {
|
|
48
|
-
if (err?.code === 'ENOENT') return { models: {}, error: null }
|
|
49
74
|
return { models: {}, error: err.message }
|
|
50
75
|
}
|
|
51
76
|
}
|
|
@@ -56,8 +81,8 @@ export async function readModelRegistry(home = process.env.HOME) {
|
|
|
56
81
|
* whether to warn.
|
|
57
82
|
* @returns {Promise<{previous: object|null, written: boolean, error: string|null}>}
|
|
58
83
|
*/
|
|
59
|
-
export async function registerModel(name, { role, project, cwd }, home =
|
|
60
|
-
const { models, error } = await readModelRegistry(home)
|
|
84
|
+
export async function registerModel(name, { role, project, cwd }, home = null, options = {}) {
|
|
85
|
+
const { models, error } = await readModelRegistry(home, options)
|
|
61
86
|
if (error) return { previous: null, written: false, error }
|
|
62
87
|
|
|
63
88
|
const previous = models[name] || null
|
|
@@ -74,7 +99,7 @@ export async function registerModel(name, { role, project, cwd }, home = process
|
|
|
74
99
|
}
|
|
75
100
|
|
|
76
101
|
try {
|
|
77
|
-
const file = modelRegistryPath(home)
|
|
102
|
+
const file = modelRegistryPath(home, options)
|
|
78
103
|
await fs.mkdir(path.dirname(file), { recursive: true })
|
|
79
104
|
await fs.writeFile(file, JSON.stringify(models, null, 2) + '\n', 'utf8')
|
|
80
105
|
return { previous: null, written: true, error: null }
|
package/server/scaffold.js
CHANGED
|
@@ -31,16 +31,18 @@ Nothing here yet. Put markdown files in this folder.
|
|
|
31
31
|
|
|
32
32
|
export async function scaffoldBase({ root, structure, folders = [] }) {
|
|
33
33
|
const actions = []
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const rootAbs = path.resolve(root)
|
|
35
|
+
await fs.mkdir(rootAbs, { recursive: true })
|
|
36
|
+
actions.push({ kind: 'dir', path: rootAbs, status: 'created' })
|
|
36
37
|
|
|
37
38
|
if (structure === 'empty') return actions
|
|
38
39
|
|
|
39
40
|
const dirs = structure === 'custom' ? folders.map(cleanRel).filter(Boolean) : STANDARD_DIRS
|
|
40
41
|
|
|
41
|
-
await writeFileSafe(path.join(
|
|
42
|
+
await writeFileSafe(path.join(rootAbs, 'README.md'), ROOT_README, actions)
|
|
42
43
|
for (const dir of dirs) {
|
|
43
|
-
const abs = path.
|
|
44
|
+
const abs = path.resolve(rootAbs, dir)
|
|
45
|
+
if (!abs.startsWith(rootAbs + path.sep)) continue
|
|
44
46
|
await fs.mkdir(abs, { recursive: true })
|
|
45
47
|
actions.push({ kind: 'dir', path: abs, status: 'created' })
|
|
46
48
|
await writeFileSafe(path.join(abs, 'README.md'), SECTION_README(dir), actions)
|
|
@@ -78,6 +80,89 @@ export async function ensureGitignore({ cwd }) {
|
|
|
78
80
|
return { path: abs, status: 'created' }
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
export function buildAgentInstructionBlock(hasGraphify = false) {
|
|
84
|
+
const fallback = hasGraphify
|
|
85
|
+
? `- Graphify is the fallback. Use it only when a specific need remains unresolved by KNL-View or the user explicitly requests Graphify. If KNL-View answers the task, stop; do not run Graphify for duplicate analysis.
|
|
86
|
+
- When the user explicitly invokes \`/graphify\`, honor that request without automatically running both tools.
|
|
87
|
+
|
|
88
|
+
`
|
|
89
|
+
: ''
|
|
90
|
+
|
|
91
|
+
return `<!-- knl-view:start -->
|
|
92
|
+
# Knowledge graph tools
|
|
93
|
+
- KNL-View is the primary graph tool. For \`.knowledges\`, source graphs, and project graph questions, read the project's \`knl-view\` skill and use the relevant KNL-View feature first.
|
|
94
|
+
${fallback}## KNL-View graph access
|
|
95
|
+
- Use the KNL-View feature that fits the task (\`graph trace\`, \`sources trace/sync/collect\`, \`model check/new\`, consensus commands, or the viewer). Do not approximate a supported KNL-View operation with ad-hoc graph queries.
|
|
96
|
+
- Route all graph reads and writes through KNL-View's documented CLI, viewer, or application endpoints. Never connect to AnchorDB directly through AnchorWire, its CLI, direct database endpoints, files, SQL, or custom clients/scripts. If KNL-View cannot perform a requested operation, explain the limitation; do not bypass it.
|
|
97
|
+
- Keep graph work targeted: specify the relevant node/path, direction, and smallest useful hop count; summarize only relevant nodes and edges; avoid dumping whole graphs or large raw JSON.
|
|
98
|
+
- Follow the KNL-View skill's trace-before-edit and \`sources sync\`-after-change rules. Treat edge direction as lineage; do not claim causality without separate evidence.
|
|
99
|
+
<!-- knl-view:end -->`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function stripLegacyKnlInstructions(text) {
|
|
103
|
+
let cleaned = text
|
|
104
|
+
cleaned = cleaned.replace(/<!-- knl-view:start -->[\s\S]*?<!-- knl-view:end -->/g, '\n')
|
|
105
|
+
cleaned = cleaned.replace(/<!--\s*knl-view:(?:start|end)\s*-->/g, '\n')
|
|
106
|
+
cleaned = cleaned.replace(
|
|
107
|
+
/(?:^|\n)# Knowledge graph tools[\s\S]*?(?=(?:\n#+\s)|$)/g,
|
|
108
|
+
'\n'
|
|
109
|
+
)
|
|
110
|
+
cleaned = cleaned.replace(
|
|
111
|
+
/(?:^|\n)## KNL-View graph access[\s\S]*?(?=(?:\n#+\s)|$)/g,
|
|
112
|
+
'\n'
|
|
113
|
+
)
|
|
114
|
+
return cleaned.trim()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function seedAgentInstructions({ cwd = process.cwd(), home = process.env.HOME, hasGraphify = false }) {
|
|
118
|
+
const block = buildAgentInstructionBlock(hasGraphify)
|
|
119
|
+
const candidatePaths = [
|
|
120
|
+
// Project files:
|
|
121
|
+
path.join(cwd, 'AGENTS.md'),
|
|
122
|
+
path.join(cwd, 'GEMINI.md'),
|
|
123
|
+
path.join(cwd, 'CLAUDE.md'),
|
|
124
|
+
// Global files:
|
|
125
|
+
path.join(home || '', '.codex', 'AGENTS.md'),
|
|
126
|
+
path.join(home || '', '.gemini', 'GEMINI.md'),
|
|
127
|
+
path.join(home || '', '.claude', 'CLAUDE.md'),
|
|
128
|
+
path.join(home || '', '.agents', 'AGENTS.md')
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
const actions = []
|
|
132
|
+
|
|
133
|
+
for (const abs of candidatePaths) {
|
|
134
|
+
const parentDir = path.dirname(abs)
|
|
135
|
+
const parentExists = await statOrNull(parentDir)
|
|
136
|
+
if (!parentExists) continue
|
|
137
|
+
|
|
138
|
+
const stat = await statOrNull(abs)
|
|
139
|
+
if (stat && stat.isFile()) {
|
|
140
|
+
const content = await fs.readFile(abs, 'utf8')
|
|
141
|
+
const cleaned = stripLegacyKnlInstructions(content)
|
|
142
|
+
const next = cleaned.length ? `${cleaned}\n\n${block}\n` : `${block}\n`
|
|
143
|
+
|
|
144
|
+
if (next !== content) {
|
|
145
|
+
await fs.writeFile(abs, next, 'utf8')
|
|
146
|
+
actions.push({ path: abs, status: 'updated' })
|
|
147
|
+
} else {
|
|
148
|
+
actions.push({ path: abs, status: 'skipped' })
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// If no project file was found/updated, create ./AGENTS.md
|
|
154
|
+
const projectAgent = path.join(cwd, 'AGENTS.md')
|
|
155
|
+
if (!actions.some((a) => a.path.startsWith(cwd))) {
|
|
156
|
+
const stat = await statOrNull(projectAgent)
|
|
157
|
+
if (!stat) {
|
|
158
|
+
await fs.writeFile(projectAgent, `${block}\n`, 'utf8')
|
|
159
|
+
actions.push({ path: projectAgent, status: 'created' })
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return actions
|
|
164
|
+
}
|
|
165
|
+
|
|
81
166
|
async function writeFileSafe(abs, content, actions) {
|
|
82
167
|
const exists = await statOrNull(abs)
|
|
83
168
|
if (exists) {
|
|
@@ -89,7 +174,12 @@ async function writeFileSafe(abs, content, actions) {
|
|
|
89
174
|
}
|
|
90
175
|
|
|
91
176
|
function cleanRel(value) {
|
|
92
|
-
|
|
177
|
+
const cleaned = String(value || '').trim()
|
|
178
|
+
const normalized = path.normalize(cleaned).replace(/^(\.\.[\/\\])+/, '').replace(/^[\/\\]+/, '').replace(/[\/\\]+$/, '')
|
|
179
|
+
if (!normalized || normalized === '.' || normalized === '..' || normalized.startsWith('..' + path.sep)) {
|
|
180
|
+
return ''
|
|
181
|
+
}
|
|
182
|
+
return normalized
|
|
93
183
|
}
|
|
94
184
|
|
|
95
185
|
async function statOrNull(abs) {
|
|
@@ -152,10 +242,10 @@ then folders.
|
|
|
152
242
|
},
|
|
153
243
|
"files": {
|
|
154
244
|
"personal-assistant.md": { "title": "Personal Assistant" },
|
|
155
|
-
"
|
|
245
|
+
"secret.md": { "ignore": true }
|
|
156
246
|
},
|
|
157
247
|
"dirs": {
|
|
158
|
-
"
|
|
248
|
+
"old-drafts": { "ignore": true }
|
|
159
249
|
}
|
|
160
250
|
}
|
|
161
251
|
\`\`\`
|
|
@@ -173,7 +263,7 @@ then folders.
|
|
|
173
263
|
bring it back:
|
|
174
264
|
|
|
175
265
|
\`\`\`json
|
|
176
|
-
{ "files": { "
|
|
266
|
+
{ "files": { "secret.md": { "ignore": true } }, "dirs": { "old-drafts": { "ignore": true } } }
|
|
177
267
|
\`\`\`
|
|
178
268
|
|
|
179
269
|
Paths are relative to the \`.knowledges\` root, use \`/\` separators, and
|
|
@@ -276,6 +366,12 @@ file or symbol is tombstoned (attribute \`tombstone = 2\`) after its
|
|
|
276
366
|
edges are unlinked; if it comes back, the tombstone lifts and the edges
|
|
277
367
|
are relinked.
|
|
278
368
|
|
|
369
|
+
External modules graph (third-party dependencies): stored in global AnchorDB
|
|
370
|
+
models per ecosystem (\`modules-npm\`, \`modules-go\`, \`modules-cargo\`, \`modules-pypi\`)
|
|
371
|
+
and cataloged in \`~/.knlview/modules/<eco>.json\`. Built on-demand via
|
|
372
|
+
\`knl-view modules build-graph <eco>::<name>\`, avoiding polluting project models.
|
|
373
|
+
Traceable via \`knl-view modules trace <eco>::<name> [symbol]\`.
|
|
374
|
+
|
|
279
375
|
Decision history skill: recording architectural decisions is delegated
|
|
280
376
|
to the separate decision-history skill (its decision block format is its
|
|
281
377
|
own domain; this skill never writes decision blocks). If it is not
|
|
@@ -340,7 +436,7 @@ of that directory.
|
|
|
340
436
|
|
|
341
437
|
Every roast must state:
|
|
342
438
|
- \`Severity\`: \`P0|P1|P2|P3\`
|
|
343
|
-
- \`Class\`: \`BUG|PERF|VULN|DEBT|CEILING|
|
|
439
|
+
- \`Class\`: \`BUG|PERF|VULN|DEBT|CEILING|PROCESS\`
|
|
344
440
|
|
|
345
441
|
## When the agent triggers the graph
|
|
346
442
|
|
|
@@ -364,6 +460,10 @@ this.
|
|
|
364
460
|
- Before renaming a symbol or moving it to another file: remember its
|
|
365
461
|
record id contains the old name/path. Sync tombstones the stale record
|
|
366
462
|
and creates the new one; trace by the new id afterwards.
|
|
463
|
+
- Before reading raw third-party library source files:
|
|
464
|
+
run \`knl-view modules build-graph <eco>::<module>\` (e.g. \`npm::express\`, \`go::github.com/gin-gonic/gin\`)
|
|
465
|
+
to index it on-demand into global AnchorDB model \`modules-<eco>\`, then trace
|
|
466
|
+
its methods with \`knl-view modules trace <eco>::<module> [symbol]\` to save context tokens.
|
|
367
467
|
- A new architectural decision: trigger the decision-history skill
|
|
368
468
|
(install it first if missing; see "Decision history skill" above).
|
|
369
469
|
The decision block format is that skill's domain; this skill never
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{ot as e,st as t}from"./chunk-DSNLSHJZ-BM5PBFmz.js";var n=(n,r)=>t.lang.round(e.parse(n)[r]);export{n as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"./src-dClg85lX.js";import"./chunk-DSNLSHJZ-BM5PBFmz.js";import"./chunk-7S6XSTFO-B0p3o547.js";import"./chunk-YZO67HFG-DJqACL5V.js";import"./chunk-AQDTSKZB-CCxwNyyA.js";import"./chunk-UHW7PFBP-CJs0VwBY.js";import"./chunk-HAGFOTED-UCDVFuN_.js";import"./chunk-ZAUX2LTX-CWfaYtwb.js";import"./chunk-WQQL4CDK-DcjPeAfQ.js";import"./chunk-55XCU76K-QR_ARj8I.js";import"./chunk-XXDRQBXY-glA0kB5s.js";import"./chunk-VQ5W62QI-DQxjgE4_.js";import"./chunk-HNE4Q36P-C_1sakp2.js";export{n as diagram}from"./chunk-NQOKRVIF-b2D1Z4oK.js";import"./mermaid.core-DPTRaYLy.js";
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{n as e}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import"./src-dClg85lX.js";import"./chunk-DSNLSHJZ-BM5PBFmz.js";import"./chunk-7S6XSTFO-B0p3o547.js";import"./chunk-YZO67HFG-DJqACL5V.js";import"./chunk-AQDTSKZB-CCxwNyyA.js";import"./chunk-UHW7PFBP-CJs0VwBY.js";import"./chunk-HAGFOTED-UCDVFuN_.js";import"./chunk-ZAUX2LTX-CWfaYtwb.js";import"./chunk-WQQL4CDK-DcjPeAfQ.js";import"./chunk-55XCU76K-QR_ARj8I.js";import"./chunk-XXDRQBXY-glA0kB5s.js";import"./chunk-VQ5W62QI-DQxjgE4_.js";import"./chunk-HNE4Q36P-C_1sakp2.js";import{r as t,t as n}from"./chunk-NQOKRVIF-b2D1Z4oK.js";import"./mermaid.core-DPTRaYLy.js";var r=n({styles:e(e=>`${t(e)}
|
|
2
|
-
.swimlane.cluster:not([data-color-id]) rect {
|
|
3
|
-
stroke: ${e.clusterBorder} !important;
|
|
4
|
-
}
|
|
5
|
-
[data-look="neo"].cluster rect {
|
|
6
|
-
filter: none;
|
|
7
|
-
}
|
|
8
|
-
`,`getStyles`)});export{r as diagram};
|