@1agh/maude 0.47.0 → 0.48.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 +7 -6
- package/apps/studio/acp/bridge.ts +8 -1
- package/apps/studio/acp/plugin-bootstrap.ts +15 -1
- package/apps/studio/build.ts +1 -1
- package/apps/studio/client/export-center.jsx +7 -6
- package/apps/studio/client/github.js +11 -4
- package/apps/studio/dist/client.bundle.js +282 -282
- package/apps/studio/dist/runtime/REMOTION-LICENSE.md +1 -1
- package/apps/studio/examples/perf-100-artboards.tsx +1 -1
- package/apps/studio/exporters/pdf.ts +35 -1
- package/apps/studio/git/service.ts +4 -1
- package/apps/studio/paths.ts +37 -1
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +34 -0
- package/apps/studio/test/acp-session-plugins.test.ts +6 -0
- package/apps/studio/test/exporters/pdf.test.ts +33 -1
- package/cli/bin/maude.mjs +1 -0
- package/cli/commands/init.mjs +80 -3
- package/cli/commands/kg.mjs +368 -0
- package/cli/commands/kg.test.mjs +118 -0
- package/cli/lib/ddr-to-kgai.mjs +648 -0
- package/cli/lib/ddr-to-kgai.test.mjs +99 -0
- package/cli/lib/flow-design-integration.test.mjs +2 -2
- package/cli/lib/gitignore-block.mjs +4 -0
- package/cli/lib/plugin-name-namespace.test.mjs +71 -0
- package/package.json +8 -8
- package/plugins/design/dependencies.json +17 -0
- package/plugins/flow/.claude-plugin/config.schema.json +66 -0
- package/plugins/flow/dependencies.json +17 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { test } from 'node:test';
|
|
6
|
+
import { buildDdrBatch } from './ddr-to-kgai.mjs';
|
|
7
|
+
|
|
8
|
+
function fixtureDir(files) {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), 'ddr-'));
|
|
10
|
+
mkdirSync(dir, { recursive: true });
|
|
11
|
+
for (const [name, body] of Object.entries(files)) writeFileSync(join(dir, name), body);
|
|
12
|
+
return dir;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DDR1 = `# DDR-001: First decision
|
|
16
|
+
|
|
17
|
+
**Status:** Accepted
|
|
18
|
+
**Date:** 2026-01-01
|
|
19
|
+
**Tags:** infra, monorepo, ci
|
|
20
|
+
|
|
21
|
+
## Context
|
|
22
|
+
The problem is X.
|
|
23
|
+
|
|
24
|
+
## Decision
|
|
25
|
+
We pick A because it is simplest and safest.
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const DDR2 = `# DDR-002: Second decision
|
|
29
|
+
|
|
30
|
+
**Status:** Accepted
|
|
31
|
+
**Date:** 2026-02-02
|
|
32
|
+
**Tags:** auth
|
|
33
|
+
|
|
34
|
+
**Supersedes:** DDR-001
|
|
35
|
+
**Related:** DDR-003
|
|
36
|
+
|
|
37
|
+
## Decision
|
|
38
|
+
We replace the earlier approach. See DDR-001 for history and DDR-003 alongside.
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
test('each DDR yields one decision with area + title + date + rationale', () => {
|
|
42
|
+
const { batch, stats } = buildDdrBatch(fixtureDir({ 'DDR-001-x.md': DDR1 }), {});
|
|
43
|
+
assert.equal(batch.decisions.length, 1);
|
|
44
|
+
const d = batch.decisions[0];
|
|
45
|
+
assert.equal(d.title, 'First decision');
|
|
46
|
+
assert.equal(d.date, '2026-01-01');
|
|
47
|
+
assert.match(d.rationale, /simplest and safest/);
|
|
48
|
+
// area = primary tag, decision element, ABOUT link
|
|
49
|
+
const kinds = d.mutations.map((m) => `${m.op}:${m.kind || m.link || ''}`);
|
|
50
|
+
assert.ok(kinds.includes('upsert_element:area'));
|
|
51
|
+
assert.ok(kinds.includes('upsert_element:decision'));
|
|
52
|
+
assert.ok(kinds.includes('add_link:ABOUT'));
|
|
53
|
+
assert.equal(stats.files, 1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('secondary tags become topic elements + TOUCHES links', () => {
|
|
57
|
+
const { batch } = buildDdrBatch(fixtureDir({ 'DDR-001-x.md': DDR1 }), {});
|
|
58
|
+
const muts = batch.decisions[0].mutations;
|
|
59
|
+
const topics = muts
|
|
60
|
+
.filter((m) => m.op === 'upsert_element' && m.kind === 'topic')
|
|
61
|
+
.map((m) => m.name);
|
|
62
|
+
assert.deepEqual(topics.sort(), ['ci', 'monorepo']); // primary 'infra' is the area, not a topic
|
|
63
|
+
assert.ok(muts.some((m) => m.link === 'TOUCHES'));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('typed Supersedes marker wins; bare mention does not duplicate it', () => {
|
|
67
|
+
const { batch } = buildDdrBatch(fixtureDir({ 'DDR-002-y.md': DDR2 }), {});
|
|
68
|
+
const links = batch.decisions[0].mutations.filter((m) => m.op === 'add_link');
|
|
69
|
+
const sup = links.filter((l) => l.link === 'SUPERSEDES' && l.to === 'decision:DDR-001');
|
|
70
|
+
assert.equal(
|
|
71
|
+
sup.length,
|
|
72
|
+
1,
|
|
73
|
+
'exactly one SUPERSEDES → DDR-001 (typed marker, not duplicated by the bare mention)'
|
|
74
|
+
);
|
|
75
|
+
// DDR-003 came from **Related:** → REFERENCES
|
|
76
|
+
assert.ok(links.some((l) => l.link === 'REFERENCES' && l.to === 'decision:DDR-003'));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('scope tags are added per decision when scope is set (model A)', () => {
|
|
80
|
+
const { batch } = buildDdrBatch(fixtureDir({ 'DDR-001-x.md': DDR1 }), {
|
|
81
|
+
repo: 'maude',
|
|
82
|
+
dept: 'dev',
|
|
83
|
+
});
|
|
84
|
+
const muts = batch.decisions[0].mutations;
|
|
85
|
+
assert.ok(muts.some((m) => m.op === 'upsert_element' && m.kind === 'repo' && m.name === 'maude'));
|
|
86
|
+
assert.ok(muts.some((m) => m.link === 'IN_REPO' && m.to === 'repo:maude'));
|
|
87
|
+
assert.ok(muts.some((m) => m.link === 'IN_DEPT' && m.to === 'dept:dev'));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('no scope ⇒ no scope mutations', () => {
|
|
91
|
+
const { batch } = buildDdrBatch(fixtureDir({ 'DDR-001-x.md': DDR1 }), {});
|
|
92
|
+
const muts = batch.decisions[0].mutations;
|
|
93
|
+
assert.ok(!muts.some((m) => m.link === 'IN_REPO' || m.link === 'IN_DEPT'));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('non-DDR files are ignored', () => {
|
|
97
|
+
const { batch } = buildDdrBatch(fixtureDir({ 'DDR-001-x.md': DDR1, 'README.md': '# index' }), {});
|
|
98
|
+
assert.equal(batch.decisions.length, 1);
|
|
99
|
+
});
|
|
@@ -158,8 +158,8 @@ test('wiring: ddr-keeper + record-ddr have the canvas-reference prompt', () => {
|
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
test('wiring: DDR-066 exists and is indexed', () => {
|
|
161
|
-
const ddr = read('.ai/decisions/DDR-066-soft-handoff-prompt-in-flow-done.md');
|
|
161
|
+
const ddr = read('.ai/archive/decisions/DDR-066-soft-handoff-prompt-in-flow-done.md');
|
|
162
162
|
assert.match(ddr, /soft prompt/i);
|
|
163
|
-
const index = read('.ai/decisions/README.md');
|
|
163
|
+
const index = read('.ai/archive/decisions/README.md');
|
|
164
164
|
assert.match(index, /DDR-066/);
|
|
165
165
|
});
|
|
@@ -51,6 +51,10 @@ export function buildBlock(designRel = '.design') {
|
|
|
51
51
|
`${root}/_chat/`, // ACP transcripts (per-machine)
|
|
52
52
|
`${root}/_untrusted/`, // hub-synced untrusted file mirror (DDR-054)
|
|
53
53
|
`${root}/_comments/`, // hub-sync-only collab comments (DDR-102/DDR-115 — never git)
|
|
54
|
+
// kgai knowledge-graph projection (repo-root, NOT under the design root) —
|
|
55
|
+
// per-machine append-only store, rebuilds from the remote on `kg sync`
|
|
56
|
+
// (feature-kgai-ecosystem-integration, DDR-115). kgai itself also ignores it.
|
|
57
|
+
'.kgai/',
|
|
54
58
|
END_MARKER,
|
|
55
59
|
];
|
|
56
60
|
return `${lines.join('\n')}\n`;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// DDR-006 (superseded) regression guard.
|
|
2
|
+
//
|
|
3
|
+
// Claude Code now prepends the `<plugin>:` namespace to every plugin
|
|
4
|
+
// command/agent/skill itself (fixed upstream in Claude Code 2.1.216 — see
|
|
5
|
+
// changelog "Fixed plugin skills with a `name` frontmatter field losing
|
|
6
|
+
// their plugin prefix in slash-command autocomplete" — and Claude Code
|
|
7
|
+
// 2.1.218 explicitly reserves `:` in agent names for that namespacing).
|
|
8
|
+
// Baking the prefix into our own `name:` frontmatter, which DDR-006
|
|
9
|
+
// (2026-05-13) required as a workaround for the OLD (opposite) bug, now
|
|
10
|
+
// produces a doubled prefix — `/design:design:new`, `flow:flow:a11y-auditor`.
|
|
11
|
+
//
|
|
12
|
+
// Two invariants this guards:
|
|
13
|
+
// 1. `name:` frontmatter in plugins/{design,flow}/{commands,agents,skills}/
|
|
14
|
+
// must be the BARE slug — no `design:`/`flow:` prefix.
|
|
15
|
+
// 2. `subagent_type:` references to our OWN plugin agents must carry
|
|
16
|
+
// exactly ONE `<plugin>:` prefix (the form the runtime's own namespace
|
|
17
|
+
// + our bare `name:` resolves to) — never zero (broken lookup against
|
|
18
|
+
// a namespaced registry) and never two (stale pre-fix leftover).
|
|
19
|
+
|
|
20
|
+
import assert from 'node:assert/strict';
|
|
21
|
+
import { execSync } from 'node:child_process';
|
|
22
|
+
import { test } from 'node:test';
|
|
23
|
+
|
|
24
|
+
function grep(pattern, extraArgs = '') {
|
|
25
|
+
const out = execSync(
|
|
26
|
+
`grep -rn ${extraArgs} '${pattern}' plugins/design plugins/flow --include='*.md' || true`,
|
|
27
|
+
{ encoding: 'utf8' }
|
|
28
|
+
);
|
|
29
|
+
return out.trim().split('\n').filter(Boolean);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('no plugin name: frontmatter carries a redundant design:/flow: prefix', () => {
|
|
33
|
+
const offenders = grep('^name: \\(design\\|flow\\):');
|
|
34
|
+
assert.deepEqual(
|
|
35
|
+
offenders,
|
|
36
|
+
[],
|
|
37
|
+
`These files declare name: with the plugin prefix baked in — Claude Code adds the plugin namespace itself now, so this doubles into e.g. /design:design:new (DDR-006 superseded):\n ${offenders.join('\n ')}`
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('no doubled design:design: / flow:flow: namespace anywhere in plugin markdown', () => {
|
|
42
|
+
const offenders = grep('design:design:\\|flow:flow:');
|
|
43
|
+
assert.deepEqual(
|
|
44
|
+
offenders,
|
|
45
|
+
[],
|
|
46
|
+
`Doubled plugin namespace found — strip one level (DDR-006 superseded):\n ${offenders.join('\n ')}`
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('subagent_type references to our own plugin agents carry exactly one <plugin>: prefix', () => {
|
|
51
|
+
// Bare `subagent_type: <slug>` where <slug> looks like one of our
|
|
52
|
+
// agent-style names (-critic/-agent/-auditor/-hacker/-keeper/-director/
|
|
53
|
+
// -analyst suffix) but has no plugin prefix — excludes Claude Code
|
|
54
|
+
// built-ins (e.g. code-simplifier, which is not one of our plugin agents)
|
|
55
|
+
// and the `"<critic-name>"` template placeholder.
|
|
56
|
+
const bareOffenders = grep(
|
|
57
|
+
'subagent_type:.*"\\?[a-z][a-z0-9-]*\\(-critic\\|-agent\\|-auditor\\|-hacker\\|-keeper\\|-director\\|-analyst\\)"\\?'
|
|
58
|
+
).filter((line) => !/design:|flow:|<critic-name>|code-simplifier/.test(line));
|
|
59
|
+
assert.deepEqual(
|
|
60
|
+
bareOffenders,
|
|
61
|
+
[],
|
|
62
|
+
`These subagent_type references to a plugin-owned agent are missing the <plugin>: prefix and will fail to resolve against the namespaced registry:\n ${bareOffenders.join('\n ')}`
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const doubledOffenders = grep('subagent_type:.*\\(design:design:\\|flow:flow:\\)');
|
|
66
|
+
assert.deepEqual(
|
|
67
|
+
doubledOffenders,
|
|
68
|
+
[],
|
|
69
|
+
`These subagent_type references are double-prefixed (DDR-006 superseded):\n ${doubledOffenders.join('\n ')}`
|
|
70
|
+
);
|
|
71
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1agh/maude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "Marketplace of Claude Code plugins by Michal Dovrtěl: `design` (canvas-first design iteration) + `flow` (generic agentic workflow loop with .ai second brain). Ships the `maude` CLI (with `mdcc` legacy alias) to scaffold workspace, run the design dev server, and manage configs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -51,13 +51,13 @@
|
|
|
51
51
|
"test:e2e:desktop:acp-cold-start": "pnpm --filter @maude/desktop-e2e e2e:acp-cold-start"
|
|
52
52
|
},
|
|
53
53
|
"optionalDependencies": {
|
|
54
|
-
"@1agh/maude-darwin-arm64": "0.
|
|
55
|
-
"@1agh/maude-darwin-x64": "0.
|
|
56
|
-
"@1agh/maude-linux-arm64": "0.
|
|
57
|
-
"@1agh/maude-linux-arm64-musl": "0.
|
|
58
|
-
"@1agh/maude-linux-x64": "0.
|
|
59
|
-
"@1agh/maude-linux-x64-musl": "0.
|
|
60
|
-
"@1agh/maude-win32-x64": "0.
|
|
54
|
+
"@1agh/maude-darwin-arm64": "0.48.0",
|
|
55
|
+
"@1agh/maude-darwin-x64": "0.48.0",
|
|
56
|
+
"@1agh/maude-linux-arm64": "0.48.0",
|
|
57
|
+
"@1agh/maude-linux-arm64-musl": "0.48.0",
|
|
58
|
+
"@1agh/maude-linux-x64": "0.48.0",
|
|
59
|
+
"@1agh/maude-linux-x64-musl": "0.48.0",
|
|
60
|
+
"@1agh/maude-win32-x64": "0.48.0"
|
|
61
61
|
},
|
|
62
62
|
"files": [
|
|
63
63
|
"cli",
|
|
@@ -146,6 +146,23 @@
|
|
|
146
146
|
"usedBy": ["commands/init.md", "skills/design/SKILL.md"],
|
|
147
147
|
"docsUrl": "https://github.com/1aGh/maude"
|
|
148
148
|
},
|
|
149
|
+
{
|
|
150
|
+
"id": "kg",
|
|
151
|
+
"type": "cli",
|
|
152
|
+
"hardness": "soft",
|
|
153
|
+
"check": {
|
|
154
|
+
"command": "kg version",
|
|
155
|
+
"expectExit": 0
|
|
156
|
+
},
|
|
157
|
+
"install": {
|
|
158
|
+
"preferred": "See docs/kgai-onboarding.md — download the pinned kgai release binary (kgai is capability-gated + opt-out; nothing to install to use classic .ai/ mode)",
|
|
159
|
+
"darwin": "curl -fsSL https://github.com/kgaidev/kgai/releases/download/v0.1.9/kg-darwin-arm64 -o /usr/local/bin/kg && chmod +x /usr/local/bin/kg"
|
|
160
|
+
},
|
|
161
|
+
"autoInstall": false,
|
|
162
|
+
"fallbackBehavior": "Design DS/canvas/brand decisions are not recorded to the kgai graph; the classic .ai/ path (via /flow:record-ddr) and on-disk sidecars are unchanged.",
|
|
163
|
+
"usedBy": ["skills/design-system/SKILL.md", "commands/edit.md"],
|
|
164
|
+
"docsUrl": "https://github.com/kgaidev/kgai"
|
|
165
|
+
},
|
|
149
166
|
{
|
|
150
167
|
"id": "jq",
|
|
151
168
|
"type": "system-tool",
|
|
@@ -305,6 +305,72 @@
|
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
307
|
},
|
|
308
|
+
"knowledgeGraph": {
|
|
309
|
+
"type": "object",
|
|
310
|
+
"additionalProperties": false,
|
|
311
|
+
"description": "Opt-OUT, capability-gated knowledge-graph memory backend (kgai). ON by default when the capability exists: an absent `knowledgeGraph` block is treated as `mode:auto`, so when the `kg` CLI is on PATH AND a store is resolvable, flow/design decision reads/writes route through the event-sourced graph instead of `.ai/decisions/` + `.ai/state/STATE.md`. When `kg` is absent, the store is unreachable, or `mode:off`, everything falls back to today's `.ai/` file behavior — no regression, nothing to configure downstream. Mirrors the `orchestration` capability-gate (DDR-130). Design plugin reads the SAME block. See the `kgai-backend` skill for the resolver.",
|
|
312
|
+
"properties": {
|
|
313
|
+
"mode": {
|
|
314
|
+
"type": "string",
|
|
315
|
+
"enum": ["auto", "on", "off"],
|
|
316
|
+
"default": "auto",
|
|
317
|
+
"description": "Opt-out default `auto` (absent block == `auto`). `auto` = active only when `kg` is on PATH AND (`store` is set OR a local `.kgai/store` exists) — conservative, mirrors `orchestration.mode:auto`; a first-run repo stays on files until the user opts in via onboarding. `on` = force kgai active (errors surface rather than silently falling back). `off` = disable entirely; today's classic `.ai/decisions/` + STATE.md path, byte-for-byte unchanged."
|
|
318
|
+
},
|
|
319
|
+
"engine": {
|
|
320
|
+
"type": "string",
|
|
321
|
+
"enum": ["kgai"],
|
|
322
|
+
"default": "kgai",
|
|
323
|
+
"description": "Knowledge-graph engine. Reserved for future engines; only `kgai` is implemented."
|
|
324
|
+
},
|
|
325
|
+
"engineVersion": {
|
|
326
|
+
"type": "string",
|
|
327
|
+
"default": "v0.1.9",
|
|
328
|
+
"description": "PINNED kgai release the CLI + desktop bundle target. Advance deliberately (harness-verified via `maude kg check-upstream`), never float — auto-tracking `latest` would let an upstream change silently alter behavior mid-rollout and is a supply-chain surface (DDR-054/056 reasoning: the bundled `kg` is third-party)."
|
|
329
|
+
},
|
|
330
|
+
"store": {
|
|
331
|
+
"type": "string",
|
|
332
|
+
"default": "",
|
|
333
|
+
"description": "Remote store the graph syncs to. `s3://company-kg/store` (shared company remote, cross-repo) | `kgai://org/project` (cloud-broker keyspace) | git remote | `\"\"` = local-only `.kgai/store`. Empty string keeps everything on this machine."
|
|
334
|
+
},
|
|
335
|
+
"scope": {
|
|
336
|
+
"type": "object",
|
|
337
|
+
"additionalProperties": false,
|
|
338
|
+
"description": "Cross-repo scope stamped on every write, so one shared store can host many repos/departments and search can bias to the local scope first. Project-agnostic — values are per-repo config, never hardcoded in the plugin.",
|
|
339
|
+
"properties": {
|
|
340
|
+
"repo": {
|
|
341
|
+
"type": "string",
|
|
342
|
+
"description": "This repo's name, stamped as a `repo:<name>` element + `IN_REPO` link on every decision."
|
|
343
|
+
},
|
|
344
|
+
"dept": {
|
|
345
|
+
"type": "string",
|
|
346
|
+
"description": "This repo's department (e.g. dev/marketing/finance/automations), stamped as a `dept:<name>` element + `IN_DEPT` link. Default search bias — reads re-rank `dept:<this>` hits first; `--all-scopes` widens."
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
"capture": {
|
|
351
|
+
"type": "object",
|
|
352
|
+
"additionalProperties": false,
|
|
353
|
+
"description": "Which write paths route into the graph when active. All default `true`.",
|
|
354
|
+
"properties": {
|
|
355
|
+
"decisions": {
|
|
356
|
+
"type": "boolean",
|
|
357
|
+
"default": true,
|
|
358
|
+
"description": "Route `/flow:record-ddr` + DDR-worthy design writes to `kg ingest` instead of a `DDR-NNN.md` file."
|
|
359
|
+
},
|
|
360
|
+
"state": {
|
|
361
|
+
"type": "boolean",
|
|
362
|
+
"default": true,
|
|
363
|
+
"description": "Route STATE.md history / pause-resume events to kgai as dated events."
|
|
364
|
+
},
|
|
365
|
+
"auto": {
|
|
366
|
+
"type": "boolean",
|
|
367
|
+
"default": true,
|
|
368
|
+
"description": "Let kgai's own `Stop` hook nudge autonomous decision capture after structural edits (zero added LLM cost). Set `false` to suppress the nudge."
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
},
|
|
308
374
|
"security": {
|
|
309
375
|
"type": "object",
|
|
310
376
|
"additionalProperties": false,
|
|
@@ -95,6 +95,23 @@
|
|
|
95
95
|
],
|
|
96
96
|
"docsUrl": "https://github.com/anthropics/agent-device"
|
|
97
97
|
},
|
|
98
|
+
{
|
|
99
|
+
"id": "kg",
|
|
100
|
+
"type": "cli",
|
|
101
|
+
"hardness": "soft",
|
|
102
|
+
"check": {
|
|
103
|
+
"command": "kg version",
|
|
104
|
+
"expectExit": 0
|
|
105
|
+
},
|
|
106
|
+
"install": {
|
|
107
|
+
"preferred": "See docs/kgai-onboarding.md — download the pinned kgai release binary (kgai is capability-gated + opt-out; nothing to install to use classic .ai/ mode)",
|
|
108
|
+
"darwin": "curl -fsSL https://github.com/kgaidev/kgai/releases/download/v0.1.9/kg-darwin-arm64 -o /usr/local/bin/kg && chmod +x /usr/local/bin/kg"
|
|
109
|
+
},
|
|
110
|
+
"autoInstall": false,
|
|
111
|
+
"fallbackBehavior": "flow/design fall back to the classic .ai/decisions/ + STATE.md file path (no regression). The kgai knowledge-graph backend (kg context/ingest/sync, cross-repo company memory) is inactive.",
|
|
112
|
+
"usedBy": ["skills/kgai-backend/SKILL.md", "commands/migrate-kgai.md"],
|
|
113
|
+
"docsUrl": "https://github.com/kgaidev/kgai"
|
|
114
|
+
},
|
|
98
115
|
{
|
|
99
116
|
"id": "jq",
|
|
100
117
|
"type": "system-tool",
|