@drafthq/draft 3.2.0 → 3.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +28 -0
- package/README.md +2 -2
- package/cli/src/hosts/cursor.js +35 -5
- package/cli/src/installer.js +20 -0
- package/cli/src/lib/cursor-registry.js +122 -0
- package/cli/src/lib/marker.js +93 -0
- package/cli/src/lib/plugin-manifest.js +20 -0
- package/core/methodology.md +1 -1
- package/core/shared/condensation.md +3 -2
- package/core/shared/git-report-metadata.md +3 -2
- package/core/shared/graph-query.md +4 -3
- package/core/shared/tool-resolver.md +71 -4
- package/core/templates/okf/ai-context-index.md +48 -0
- package/core/templates/okf/concept.md +54 -0
- package/core/templates/okf/index.md +40 -0
- package/core/templates/okf/section-index.md +25 -0
- package/core/templates/plan.md +3 -2
- package/integrations/agents/AGENTS.md +792 -102
- package/integrations/copilot/.github/copilot-instructions.md +792 -102
- package/package.json +3 -2
- package/scripts/lib.sh +10 -0
- package/scripts/tools/graph-preflight.sh +259 -0
- package/scripts/tools/okf-render-views.sh +373 -0
- package/scripts/tools/okf-validate.sh +204 -0
- package/scripts/tools/resolve-tools.sh +78 -0
- package/skills/adr/SKILL.md +3 -2
- package/skills/bughunt/SKILL.md +10 -1
- package/skills/coverage/SKILL.md +8 -3
- package/skills/debug/SKILL.md +16 -5
- package/skills/decompose/SKILL.md +29 -12
- package/skills/deep-review/SKILL.md +19 -6
- package/skills/deploy-checklist/SKILL.md +6 -5
- package/skills/graph/SKILL.md +15 -6
- package/skills/impact/SKILL.md +12 -1
- package/skills/implement/SKILL.md +20 -4
- package/skills/init/SKILL.md +36 -10
- package/skills/init/references/architecture-spec.md +17 -6
- package/skills/init/references/okf-emitter.md +223 -0
- package/skills/learn/SKILL.md +15 -4
- package/skills/quick-review/SKILL.md +13 -3
- package/skills/review/SKILL.md +32 -8
- package/skills/standup/SKILL.md +3 -2
- package/skills/status/SKILL.md +3 -2
- package/skills/tech-debt/SKILL.md +20 -6
- package/skills/upload/SKILL.md +3 -2
- package/integrations/copilot/.github/copilot-instructions.md.7iDz8X +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.DoBdtd +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.McGoBW +0 -122
- package/integrations/copilot/.github/copilot-instructions.md.VsPyLB +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.XAVr7D +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.YoFVFa +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.a9DeW0 +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.oxQs3B +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.ww33Ly +0 -91
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"name": "draft",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"description": "Context-Driven Development: draft specs and plans before implementation. Structured workflows for features and fixes.",
|
|
15
|
-
"version": "3.
|
|
15
|
+
"version": "3.3.0",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "mayurpise"
|
|
18
18
|
},
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "draft",
|
|
3
|
+
"displayName": "Draft",
|
|
4
|
+
"description": "Context-Driven Development: draft specs and plans before implementation. Structured workflows for features and fixes.",
|
|
5
|
+
"version": "3.3.0",
|
|
6
|
+
"skills": "./skills/",
|
|
7
|
+
"agents": "./core/agents/",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "mayurpise"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/drafthq/draft",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"context-driven-development",
|
|
15
|
+
"ai-assisted",
|
|
16
|
+
"planning",
|
|
17
|
+
"specifications",
|
|
18
|
+
"architecture",
|
|
19
|
+
"code-review",
|
|
20
|
+
"monorepo",
|
|
21
|
+
"tdd",
|
|
22
|
+
"validation",
|
|
23
|
+
"enterprise",
|
|
24
|
+
"incremental-refresh",
|
|
25
|
+
"signal-detection",
|
|
26
|
+
"state-management"
|
|
27
|
+
]
|
|
28
|
+
}
|
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ Each host installs the way that host actually loads extensions — no manual ste
|
|
|
67
67
|
| Host | `draft install …` | What it does |
|
|
68
68
|
|------|-------------------|--------------|
|
|
69
69
|
| **Claude Code** | `claude-code` | Registers the plugin via `claude plugin marketplace add` + `claude plugin install` (user scope). Restart Claude Code. |
|
|
70
|
-
| **Cursor** | `cursor` | Copies the plugin into `~/.cursor/plugins/local/draft
|
|
70
|
+
| **Cursor** | `cursor` | Copies the plugin into `~/.cursor/plugins/local/draft/`, writes `.cursor-plugin/plugin.json`, registers `draft@draft-plugins` in Cursor's plugin registry, and enables it. Restart Cursor (or Developer: Reload Window). |
|
|
71
71
|
| **Codex** | `codex` | Writes `./AGENTS.md`, which Codex reads automatically. |
|
|
72
72
|
| **opencode** | `opencode` | Writes `./AGENTS.md` + `~/.agents/skills/draft/`, both auto-discovered. |
|
|
73
73
|
|
|
@@ -92,7 +92,7 @@ Run `/draft` for the full command map.
|
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
### Cursor — from GitHub
|
|
95
|
-
Cursor
|
|
95
|
+
Cursor requires `.cursor-plugin/plugin.json`; the `draft install cursor` command also registers the plugin via the shared Claude plugin registry that Cursor reads on many builds. To add from source instead, use *Settings > Rules, Skills, Subagents > Rules > New > Add from Github*:
|
|
96
96
|
```
|
|
97
97
|
https://github.com/drafthq/draft.git
|
|
98
98
|
```
|
package/cli/src/hosts/cursor.js
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { asset } = require('../lib/paths');
|
|
5
|
+
const { readPluginVersion } = require('../lib/plugin-manifest');
|
|
6
|
+
const { applyCursorRegistration } = require('../lib/cursor-registry');
|
|
5
7
|
|
|
6
|
-
// Cursor
|
|
7
|
-
//
|
|
8
|
-
//
|
|
8
|
+
// Cursor discovers Draft through two layers, both shipped here: the native
|
|
9
|
+
// .cursor-plugin/plugin.json manifest, and the shared Claude plugin registry
|
|
10
|
+
// under ~/.claude/ (written in postInstall). A file copy alone is not enough —
|
|
11
|
+
// current Cursor builds never surface /draft:* commands without registration.
|
|
9
12
|
const ITEMS = [
|
|
13
|
+
{ p: '.cursor-plugin', kind: 'copyTree' },
|
|
10
14
|
{ p: '.claude-plugin', kind: 'copyTree' },
|
|
11
15
|
{ p: 'skills', kind: 'copyTree' },
|
|
12
16
|
{ p: 'core', kind: 'copyTree' },
|
|
@@ -37,14 +41,40 @@ module.exports = {
|
|
|
37
41
|
dest: path.join(base, it.p),
|
|
38
42
|
label: it.p,
|
|
39
43
|
}));
|
|
40
|
-
// Guard the whole install dir on the manifest's presence.
|
|
44
|
+
// Guard the whole install dir on the .cursor-plugin manifest's presence.
|
|
41
45
|
actions[0].guard = true;
|
|
42
46
|
|
|
43
47
|
return {
|
|
44
48
|
targetSummary: `${base} (${ctx.scope})`,
|
|
45
49
|
actions,
|
|
46
50
|
graph: true,
|
|
47
|
-
|
|
51
|
+
// Runs after the file copies: register + enable the plugin in the shared
|
|
52
|
+
// Claude registry. On a dry run it computes the merges and writes nothing.
|
|
53
|
+
postInstall(c) {
|
|
54
|
+
const installedManifest = path.join(base, '.cursor-plugin', 'plugin.json');
|
|
55
|
+
const version = readPluginVersion(
|
|
56
|
+
c.dryRun ? asset('.cursor-plugin', 'plugin.json') : installedManifest
|
|
57
|
+
);
|
|
58
|
+
return applyCursorRegistration({
|
|
59
|
+
home: c.home,
|
|
60
|
+
installPath: base,
|
|
61
|
+
version,
|
|
62
|
+
scope: c.scope,
|
|
63
|
+
dryRun: c.dryRun,
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
done: [
|
|
67
|
+
`Draft installed to ${base}.`,
|
|
68
|
+
'Plugin registered and enabled in ~/.claude/plugins/.',
|
|
69
|
+
'Restart Cursor (Developer: Reload Window) to load /draft:* commands.',
|
|
70
|
+
].join(' '),
|
|
71
|
+
fallbackTitle: 'If /draft commands do not appear after restart:',
|
|
72
|
+
fallback: [
|
|
73
|
+
`Confirm ${base}/.cursor-plugin/plugin.json exists`,
|
|
74
|
+
'Confirm ~/.claude/plugins/installed_plugins.json contains "draft@draft-plugins"',
|
|
75
|
+
'Confirm ~/.claude/settings.json has "draft@draft-plugins": true',
|
|
76
|
+
'Run Developer: Reload Window in Cursor',
|
|
77
|
+
],
|
|
48
78
|
};
|
|
49
79
|
},
|
|
50
80
|
};
|
package/cli/src/installer.js
CHANGED
|
@@ -4,6 +4,7 @@ const { spawnSync } = require('child_process');
|
|
|
4
4
|
const fsx = require('./lib/fsx');
|
|
5
5
|
const log = require('./lib/log');
|
|
6
6
|
const { fetchGraph } = require('./lib/graph');
|
|
7
|
+
const { writePluginRootMarker } = require('./lib/marker');
|
|
7
8
|
|
|
8
9
|
// A short ceiling so a wedged `claude --version` can't hang the installer
|
|
9
10
|
// before we even reach the real (separately-timed) install steps.
|
|
@@ -107,6 +108,25 @@ function install(host, ctx) {
|
|
|
107
108
|
fetchGraph(ctx);
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
// Host-specific registration (e.g. Cursor's plugin registry). On a real
|
|
112
|
+
// install the hook performs the writes (logging each path); on a dry run it
|
|
113
|
+
// returns the planned paths so we can surface them without touching disk.
|
|
114
|
+
if (plan.postInstall) {
|
|
115
|
+
const reg = plan.postInstall(ctx);
|
|
116
|
+
if (reg && ctx.dryRun) {
|
|
117
|
+
log.plan(`would update registry: ${reg.kmPath}`);
|
|
118
|
+
log.plan(`would update registry: ${reg.ipPath}`);
|
|
119
|
+
log.plan(`would update registry: ${reg.settingsPath}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Record the install path so skills can locate scripts/tools/ from the user's
|
|
124
|
+
// project cwd (best-effort; graph skills glob-fallback if the marker is absent).
|
|
125
|
+
if (!ctx.dryRun) {
|
|
126
|
+
const root = writePluginRootMarker(host.id);
|
|
127
|
+
if (root) log.note(`Recorded plugin path for graph tooling: ${root}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
110
130
|
(plan.notes || []).forEach((n) => log.note(n));
|
|
111
131
|
|
|
112
132
|
if (ctx.dryRun) {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Cursor reads plugins from the shared Claude plugin registry under ~/.claude/
|
|
4
|
+
// on many builds, so a file copy alone never surfaces /draft:* commands. This
|
|
5
|
+
// module merges Draft into the three registry files non-destructively:
|
|
6
|
+
// - ~/.claude/plugins/known_marketplaces.json (marketplace entry)
|
|
7
|
+
// - ~/.claude/plugins/installed_plugins.json (install record)
|
|
8
|
+
// - ~/.claude/settings.json (enabledPlugins flag)
|
|
9
|
+
// Every write preserves all other plugins, hooks, and unknown keys. Reads of a
|
|
10
|
+
// corrupt JSON file fail loud rather than silently clobbering user data.
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const log = require('./log');
|
|
14
|
+
|
|
15
|
+
const MARKETPLACE_KEY = 'draft-plugins';
|
|
16
|
+
const PLUGIN_KEY = `draft@${MARKETPLACE_KEY}`; // name@<marketplace name>
|
|
17
|
+
|
|
18
|
+
function readJson(filePath, fallback) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (err.code === 'ENOENT') return fallback;
|
|
23
|
+
throw new Error(`Cannot parse ${filePath}: ${err.message}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function writeJsonAtomic(filePath, data) {
|
|
28
|
+
const dir = path.dirname(filePath);
|
|
29
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
30
|
+
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
31
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
32
|
+
fs.renameSync(tmp, filePath);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function claudeHome(home) {
|
|
36
|
+
return path.join(home, '.claude');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function registryPaths(home) {
|
|
40
|
+
const base = path.join(claudeHome(home), 'plugins');
|
|
41
|
+
return {
|
|
42
|
+
kmPath: path.join(base, 'known_marketplaces.json'),
|
|
43
|
+
ipPath: path.join(base, 'installed_plugins.json'),
|
|
44
|
+
settingsPath: path.join(claudeHome(home), 'settings.json'),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function registryScope(scope) {
|
|
49
|
+
return scope === 'project' ? 'project' : 'user';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Pure: compute the merged registry objects without touching disk. Callers that
|
|
53
|
+
// want to persist them use applyCursorRegistration (which delegates here first).
|
|
54
|
+
function registerCursorPlugin(opts) {
|
|
55
|
+
const { home, version, scope } = opts;
|
|
56
|
+
const installPath = path.resolve(opts.installPath);
|
|
57
|
+
const now = new Date().toISOString();
|
|
58
|
+
const paths = registryPaths(home);
|
|
59
|
+
|
|
60
|
+
// --- known_marketplaces.json: overwrite only our key. ---
|
|
61
|
+
const km = readJson(paths.kmPath, {});
|
|
62
|
+
km[MARKETPLACE_KEY] = {
|
|
63
|
+
source: { source: 'directory', path: installPath },
|
|
64
|
+
installLocation: installPath,
|
|
65
|
+
lastUpdated: now,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// --- installed_plugins.json: merge our key, preserve installedAt on upgrade. ---
|
|
69
|
+
const ip = readJson(paths.ipPath, { version: 2, plugins: {} });
|
|
70
|
+
if (typeof ip.version !== 'number') ip.version = 2;
|
|
71
|
+
if (!ip.plugins || typeof ip.plugins !== 'object') ip.plugins = {};
|
|
72
|
+
const existing = Array.isArray(ip.plugins[PLUGIN_KEY]) ? ip.plugins[PLUGIN_KEY][0] : null;
|
|
73
|
+
const installedAt = existing && existing.installedAt ? existing.installedAt : now;
|
|
74
|
+
ip.plugins[PLUGIN_KEY] = [
|
|
75
|
+
{
|
|
76
|
+
scope: registryScope(scope),
|
|
77
|
+
installPath,
|
|
78
|
+
version,
|
|
79
|
+
installedAt,
|
|
80
|
+
lastUpdated: now,
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
// --- settings.json: flip our enabledPlugins flag, preserve everything else. ---
|
|
85
|
+
const settings = readJson(paths.settingsPath, {});
|
|
86
|
+
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== 'object') {
|
|
87
|
+
settings.enabledPlugins = {};
|
|
88
|
+
}
|
|
89
|
+
settings.enabledPlugins[PLUGIN_KEY] = true;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
kmPath: paths.kmPath,
|
|
93
|
+
km,
|
|
94
|
+
ipPath: paths.ipPath,
|
|
95
|
+
ip,
|
|
96
|
+
settingsPath: paths.settingsPath,
|
|
97
|
+
settings,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Compute the merges and, unless dryRun, persist them atomically. Returns the
|
|
102
|
+
// same shape as registerCursorPlugin so the installer can log the paths.
|
|
103
|
+
function applyCursorRegistration(opts) {
|
|
104
|
+
const result = registerCursorPlugin(opts);
|
|
105
|
+
if (opts.dryRun) return result;
|
|
106
|
+
|
|
107
|
+
log.plan(`writing: ${result.kmPath}`);
|
|
108
|
+
writeJsonAtomic(result.kmPath, result.km);
|
|
109
|
+
log.plan(`writing: ${result.ipPath}`);
|
|
110
|
+
writeJsonAtomic(result.ipPath, result.ip);
|
|
111
|
+
log.plan(`writing: ${result.settingsPath}`);
|
|
112
|
+
writeJsonAtomic(result.settingsPath, result.settings);
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
PLUGIN_KEY,
|
|
119
|
+
MARKETPLACE_KEY,
|
|
120
|
+
registerCursorPlugin,
|
|
121
|
+
applyCursorRegistration,
|
|
122
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Write the install-path marker (~/.cache/draft/plugin-root) so a draft skill can
|
|
4
|
+
// locate its bundled scripts/tools/ from the user's project cwd. Skills run with
|
|
5
|
+
// cwd = the user's project and ${CLAUDE_PLUGIN_ROOT} is NOT exported into skill Bash,
|
|
6
|
+
// so without this marker resolution falls back to globbing the plugin cache. The
|
|
7
|
+
// marker is the fast, authoritative path; see core/shared/tool-resolver.md.
|
|
8
|
+
//
|
|
9
|
+
// Best-effort: any failure is swallowed — graph skills still resolve via the glob
|
|
10
|
+
// fallback, so a missing marker is never fatal.
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
// Resolve the installed draft plugin root for a given host, or null if unknown.
|
|
17
|
+
function resolvePluginRoot(hostId) {
|
|
18
|
+
const home = os.homedir();
|
|
19
|
+
|
|
20
|
+
if (hostId === 'claude-code') {
|
|
21
|
+
// 1. Claude Code's own registry holds the authoritative installPath.
|
|
22
|
+
const reg = path.join(home, '.claude', 'plugins', 'installed_plugins.json');
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(fs.readFileSync(reg, 'utf8'));
|
|
25
|
+
const key = Object.keys(data.plugins || {}).find((k) => k.startsWith('draft@'));
|
|
26
|
+
const ip = key && data.plugins[key] && data.plugins[key][0] && data.plugins[key][0].installPath;
|
|
27
|
+
if (ip && fs.existsSync(path.join(ip, 'scripts', 'tools'))) return ip;
|
|
28
|
+
} catch {
|
|
29
|
+
/* registry missing or unparseable — fall through to the cache scan */
|
|
30
|
+
}
|
|
31
|
+
// 2. Fallback: newest versioned dir under the plugin cache.
|
|
32
|
+
return newestCacheRoot(path.join(home, '.claude', 'plugins', 'cache'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (hostId === 'cursor') {
|
|
36
|
+
const p = path.join(home, '.cursor', 'plugins', 'local', 'draft');
|
|
37
|
+
return fs.existsSync(path.join(p, 'scripts', 'tools')) ? p : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Newest <cache>/<marketplace>/draft/<version> dir that carries scripts/tools.
|
|
44
|
+
function newestCacheRoot(cacheDir) {
|
|
45
|
+
try {
|
|
46
|
+
const candidates = [];
|
|
47
|
+
for (const mkt of fs.readdirSync(cacheDir)) {
|
|
48
|
+
const draftDir = path.join(cacheDir, mkt, 'draft');
|
|
49
|
+
let versions;
|
|
50
|
+
try {
|
|
51
|
+
versions = fs.readdirSync(draftDir);
|
|
52
|
+
} catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
for (const v of versions) {
|
|
56
|
+
const root = path.join(draftDir, v);
|
|
57
|
+
if (fs.existsSync(path.join(root, 'scripts', 'tools'))) candidates.push({ v, root });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!candidates.length) return null;
|
|
61
|
+
candidates.sort((a, b) => compareVersions(a.v, b.v));
|
|
62
|
+
return candidates[candidates.length - 1].root;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Numeric-aware version compare (no semver dependency).
|
|
69
|
+
function compareVersions(a, b) {
|
|
70
|
+
const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
|
|
71
|
+
const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
|
|
72
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
73
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
74
|
+
if (d) return d;
|
|
75
|
+
}
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Write ~/.cache/draft/plugin-root for the host. Returns the path written, or null.
|
|
80
|
+
function writePluginRootMarker(hostId) {
|
|
81
|
+
try {
|
|
82
|
+
const root = resolvePluginRoot(hostId);
|
|
83
|
+
if (!root) return null;
|
|
84
|
+
const dest = path.join(os.homedir(), '.cache', 'draft', 'plugin-root');
|
|
85
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
86
|
+
fs.writeFileSync(dest, root + '\n');
|
|
87
|
+
return root;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { writePluginRootMarker, resolvePluginRoot };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Read name/version from a plugin manifest JSON (.cursor-plugin/plugin.json or
|
|
4
|
+
// .claude-plugin/plugin.json). Fails loud if either required field is missing —
|
|
5
|
+
// a manifest without a version would corrupt the registry's install record.
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
function readPluginManifest(manifestPath) {
|
|
9
|
+
const raw = fs.readFileSync(manifestPath, 'utf8');
|
|
10
|
+
const data = JSON.parse(raw);
|
|
11
|
+
if (!data.name) throw new Error(`Missing name in ${manifestPath}`);
|
|
12
|
+
if (!data.version) throw new Error(`Missing version in ${manifestPath}`);
|
|
13
|
+
return data;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readPluginVersion(manifestPath) {
|
|
17
|
+
return readPluginManifest(manifestPath).version;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { readPluginManifest, readPluginVersion };
|
package/core/methodology.md
CHANGED
|
@@ -300,7 +300,7 @@ You should see the list of available Draft commands. If not, check that the plug
|
|
|
300
300
|
|
|
301
301
|
### Supported Platforms
|
|
302
302
|
|
|
303
|
-
Draft works with **Claude Code** (native `.claude-plugin/` support) and **Cursor** (
|
|
303
|
+
Draft works with **Claude Code** (native `.claude-plugin/` support) and **Cursor** (requires `.cursor-plugin/plugin.json` plus registration in the shared Claude plugin registry, both handled by `draft install cursor`). No build pipeline required.
|
|
304
304
|
|
|
305
305
|
---
|
|
306
306
|
|
|
@@ -180,8 +180,9 @@ Write the completed content to `draft/.ai-context.md`.
|
|
|
180
180
|
After writing both output files, strip trailing whitespace and blank lines at EOF to prevent GitHub upload failures. Resolve the script via the canonical tool resolver (see [tool-resolver.md](tool-resolver.md)):
|
|
181
181
|
|
|
182
182
|
```bash
|
|
183
|
-
DRAFT_TOOLS="$
|
|
184
|
-
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$
|
|
183
|
+
DRAFT_TOOLS="$(cat ~/.cache/draft/plugin-root 2>/dev/null)/scripts/tools"
|
|
184
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/cache/*/draft/*/scripts/tools 2>/dev/null | sort -V | tail -1)"
|
|
185
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/marketplaces/*draft*/scripts/tools 2>/dev/null | tail -1)"
|
|
185
186
|
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$PWD/scripts/tools"
|
|
186
187
|
[ -x "$DRAFT_TOOLS/fix-whitespace.sh" ] && bash "$DRAFT_TOOLS/fix-whitespace.sh" draft/architecture.md draft/.ai-context.md draft/.ai-profile.md 2>/dev/null || true
|
|
187
188
|
```
|
|
@@ -16,8 +16,9 @@ Referenced by: All skills that generate Draft reports — including `/draft:bugh
|
|
|
16
16
|
Use `git-metadata.sh` from the plugin install, resolved via the canonical tool resolver (see [tool-resolver.md](tool-resolver.md)):
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
DRAFT_TOOLS="$
|
|
20
|
-
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$
|
|
19
|
+
DRAFT_TOOLS="$(cat ~/.cache/draft/plugin-root 2>/dev/null)/scripts/tools"
|
|
20
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/cache/*/draft/*/scripts/tools 2>/dev/null | sort -V | tail -1)"
|
|
21
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/marketplaces/*draft*/scripts/tools 2>/dev/null | tail -1)"
|
|
21
22
|
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$PWD/scripts/tools"
|
|
22
23
|
bash "$DRAFT_TOOLS/git-metadata.sh" --yaml \
|
|
23
24
|
--project "$PROJECT" --module "$MODULE" \
|
|
@@ -92,11 +92,12 @@ These fields are appended to `~/.draft/metrics.jsonl` along with the existing sk
|
|
|
92
92
|
|
|
93
93
|
## Tooling Wrappers
|
|
94
94
|
|
|
95
|
-
For common query modes, prefer the deterministic wrappers that ship with the plugin. Resolve their location via the canonical tool resolver (see [tool-resolver.md](tool-resolver.md)) before invoking:
|
|
95
|
+
For common query modes, prefer the deterministic wrappers that ship with the plugin. Resolve their location via the canonical tool resolver (see [tool-resolver.md](tool-resolver.md)) before invoking. Skills run with cwd = the user's project and `${CLAUDE_PLUGIN_ROOT}` is **not** exported into skill Bash, so a bare `scripts/tools/foo.sh` fails — establish `DRAFT_TOOLS` once before the first helper call, in the same Bash session as your tool calls (re-establish it if you split helper calls into a separate, later Bash block):
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
|
-
DRAFT_TOOLS="$
|
|
99
|
-
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$
|
|
98
|
+
DRAFT_TOOLS="$(cat ~/.cache/draft/plugin-root 2>/dev/null)/scripts/tools"
|
|
99
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/cache/*/draft/*/scripts/tools 2>/dev/null | sort -V | tail -1)"
|
|
100
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/marketplaces/*draft*/scripts/tools 2>/dev/null | tail -1)"
|
|
100
101
|
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$PWD/scripts/tools"
|
|
101
102
|
```
|
|
102
103
|
|
|
@@ -1,10 +1,77 @@
|
|
|
1
1
|
---
|
|
2
2
|
shared: tool-resolver
|
|
3
|
-
applies_to:
|
|
3
|
+
applies_to: every skill that invokes scripts/tools/*
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# tool-resolver
|
|
6
|
+
# tool-resolver
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Canonical procedure for locating Draft's bundled shell helpers (`scripts/tools/*.sh`)
|
|
9
|
+
from inside a skill.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
## Why this exists
|
|
12
|
+
|
|
13
|
+
When Claude runs a draft skill, the shell's **working directory is the user's
|
|
14
|
+
project**, not the plugin. The helpers live inside the plugin install directory,
|
|
15
|
+
which on a marketplace/npm install is `~/.claude/plugins/cache/<marketplace>/draft/<version>/`
|
|
16
|
+
— never the cwd. `${CLAUDE_PLUGIN_ROOT}` is **not** exported into skill-driven Bash
|
|
17
|
+
(it is only set for hooks, MCP/LSP servers, and monitor commands), so a bare
|
|
18
|
+
`scripts/tools/foo.sh` or `${CLAUDE_PLUGIN_ROOT}/...` invocation silently fails.
|
|
19
|
+
|
|
20
|
+
Every skill MUST resolve `DRAFT_TOOLS` and invoke helpers as `"$DRAFT_TOOLS/<tool>.sh"`.
|
|
21
|
+
|
|
22
|
+
## Resolution order
|
|
23
|
+
|
|
24
|
+
`DRAFT_TOOLS` resolves to the first directory that exists, in this order:
|
|
25
|
+
|
|
26
|
+
1. `${DRAFT_PLUGIN_ROOT}/scripts/tools` — explicit override (testing / pinned installs)
|
|
27
|
+
2. `$(cat ~/.cache/draft/plugin-root)/scripts/tools` — install marker written by `draft install` (authoritative)
|
|
28
|
+
3. `${CLAUDE_PLUGIN_ROOT}/scripts/tools` — set in hook/MCP contexts; harmless to probe
|
|
29
|
+
4. `installed_plugins.json → installPath` for `draft@*` — Claude Code's own registry (needs `jq`)
|
|
30
|
+
5. `~/.claude/plugins/cache/*/draft/*/scripts/tools` — newest cache install (glob, `sort -V`)
|
|
31
|
+
6. `~/.claude/plugins/marketplaces/*draft*/scripts/tools` — marketplace clone
|
|
32
|
+
7. `~/.cursor/plugins/local/draft/scripts/tools` — Cursor local install
|
|
33
|
+
8. `$PWD/scripts/tools` — dev / dogfooding (running inside the draft repo itself)
|
|
34
|
+
|
|
35
|
+
The marker (step 2) is the fast, authoritative path; steps 5–6 are the glob fallback
|
|
36
|
+
that keeps resolution working on installs predating the marker (no reinstall required).
|
|
37
|
+
|
|
38
|
+
## Skill preamble (copy verbatim)
|
|
39
|
+
|
|
40
|
+
Establish `DRAFT_TOOLS` once, before the first helper call, in the **same Bash
|
|
41
|
+
session** that runs your tool calls — exactly as skills already define `REPO_ABS`
|
|
42
|
+
once and reuse it. Env vars do not persist across **separate** Bash tool
|
|
43
|
+
invocations (only the cwd does), so if you split helper calls into a later, separate
|
|
44
|
+
Bash block, re-establish `DRAFT_TOOLS` there too:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
DRAFT_TOOLS="$(cat ~/.cache/draft/plugin-root 2>/dev/null)/scripts/tools"
|
|
48
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/cache/*/draft/*/scripts/tools 2>/dev/null | sort -V | tail -1)"
|
|
49
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/marketplaces/*draft*/scripts/tools 2>/dev/null | tail -1)"
|
|
50
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$PWD/scripts/tools"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then invoke helpers through the variable:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
"$DRAFT_TOOLS/hotspot-rank.sh" --repo . --top 5
|
|
57
|
+
"$DRAFT_TOOLS/graph-arch.sh" --repo .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The four-line inline preamble is self-contained and is the recommended form for
|
|
61
|
+
skills — it needs no marker file and no prior `source`. The full 8-step resolver
|
|
62
|
+
(adding the `${DRAFT_PLUGIN_ROOT}` override, `${CLAUDE_PLUGIN_ROOT}`, the jq-registry
|
|
63
|
+
lookup, and the Cursor path) is shipped as `scripts/tools/resolve-tools.sh` for tests
|
|
64
|
+
and for callers that prefer a single source of truth:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
DRAFT_TOOLS="$("$PWD/scripts/tools/resolve-tools.sh" 2>/dev/null)" # dev/dogfood
|
|
68
|
+
# installed: "$(cat ~/.cache/draft/plugin-root)/scripts/tools/resolve-tools.sh"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## The engine binary is separate
|
|
72
|
+
|
|
73
|
+
`DRAFT_TOOLS` locates the **wrapper scripts**. Each wrapper then resolves the
|
|
74
|
+
`codebase-memory-mcp` **engine binary** itself via `_lib.sh:find_memory_bin`
|
|
75
|
+
(`DRAFT_MEMORY_BIN` → `$PATH` → `~/.cache/draft/bin/` → vendored). Do not conflate
|
|
76
|
+
the two: a wrapper that runs but reports `source: unavailable` means the engine
|
|
77
|
+
binary is missing, not the wrapper.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
project: "{PROJECT_NAME}"
|
|
3
|
+
module: "{MODULE_NAME or 'root'}"
|
|
4
|
+
generated_by: "draft:init"
|
|
5
|
+
generated_at: "{ISO_TIMESTAMP}"
|
|
6
|
+
draft_init_mode: okf
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# {PROJECT_NAME} — AI Context Index
|
|
10
|
+
|
|
11
|
+
> Index root for the OKF taxonomy bundle (`wiki/`). Read **Synopsis** for broad
|
|
12
|
+
> tasks (they usually terminate here). For focused tasks, route through the
|
|
13
|
+
> **Concept Map** to ≤N concept pages — each lists `x-grounded-paths`. This is
|
|
14
|
+
> both the cheap broad-context path AND the progressive-disclosure entry point.
|
|
15
|
+
|
|
16
|
+
## Synopsis
|
|
17
|
+
|
|
18
|
+
<!-- 150–250 lines: the cheap broad-context path (prior .ai-context.md value
|
|
19
|
+
preserved). Architecture in brief, key invariants, where to start, top
|
|
20
|
+
hotspots. A broad task should be answerable from this section alone. -->
|
|
21
|
+
|
|
22
|
+
- **Architecture in brief:** {2–4 sentences}
|
|
23
|
+
- **Key invariants:** {bullet list, provenance-tagged}
|
|
24
|
+
- **Where to start:** {entrypoints + core subsystems}
|
|
25
|
+
- **Top hotspots:** {from hotspot-rank.sh — symbol, fan-in}
|
|
26
|
+
|
|
27
|
+
## Concept Map
|
|
28
|
+
|
|
29
|
+
<!-- Routing table built from each concept's frontmatter `description`.
|
|
30
|
+
Open a section index for the full per-concept list. -->
|
|
31
|
+
|
|
32
|
+
| Section | Routing |
|
|
33
|
+
|---------|---------|
|
|
34
|
+
| `wiki/systems/` | {one-line per subsystem — what it owns, when to open} |
|
|
35
|
+
| `wiki/features/` | {one-line per feature} |
|
|
36
|
+
| `wiki/reference/` | config, schemas, APIs, ADRs, runbooks |
|
|
37
|
+
| `wiki/entrypoints/` | binaries / CLIs / handler roots |
|
|
38
|
+
|
|
39
|
+
Full taxonomy: [wiki/index.md](wiki/index.md).
|
|
40
|
+
|
|
41
|
+
## How to navigate
|
|
42
|
+
|
|
43
|
+
1. **Broad task** (summarize, "what owns X", topology) → answer from **Synopsis**.
|
|
44
|
+
2. **Focused task** ("what breaks if I change Y", "add a field to Z") → open the
|
|
45
|
+
matching concept via the **Concept Map**; follow its `x-grounded-paths` and
|
|
46
|
+
`Used by` cross-links. Do not read the whole bundle.
|
|
47
|
+
3. Every concept page is verified against the live call graph; trust its
|
|
48
|
+
`Blast radius` section over re-deriving by hand.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Subsystem # required (OKF) — one of the frozen vocab below
|
|
3
|
+
title: "{CONCEPT_TITLE}" # OKF
|
|
4
|
+
description: > # OKF — LOAD-BEARING: the agent's routing key.
|
|
5
|
+
Write this as a ROUTING DECISION, not a summary. It must answer
|
|
6
|
+
"should the agent open this file for the task at hand?" from the index
|
|
7
|
+
alone. Name the responsibilities and the words a task would use.
|
|
8
|
+
resource: "{CANONICAL_SOURCE_PATH}" # OKF — canonical source path(s)
|
|
9
|
+
tags: [tag1, tag2] # OKF
|
|
10
|
+
timestamp: "{ISO_TIMESTAMP}" # OKF — last regeneration
|
|
11
|
+
# Draft extensions (ignored by generic OKF consumers; namespaced x-):
|
|
12
|
+
x-grounded-paths: ["{path/a}", "{path/b}"] # exact source files this page grounds
|
|
13
|
+
x-hotspot-score: 0.0 # from hotspot-rank.sh (0..1)
|
|
14
|
+
x-callers: ["{module/a}", "{module/b}"] # from graph-callers.sh
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# {CONCEPT_TITLE}
|
|
18
|
+
|
|
19
|
+
<!--
|
|
20
|
+
Frozen `type` vocabulary (changing it churns every file — versioned via
|
|
21
|
+
index.md: okf_types_version):
|
|
22
|
+
Subsystem — major graph cluster / package boundary → systems/
|
|
23
|
+
Module — single package/dir with cohesive responsibility → systems/
|
|
24
|
+
Feature — user-facing capability spanning modules → features/
|
|
25
|
+
Entrypoint — binary / main / CLI / handler root → entrypoints/
|
|
26
|
+
API — public interface, route group, RPC surface → reference/
|
|
27
|
+
DataModel — schema, table, core struct/type → reference/
|
|
28
|
+
Dependency — notable external dep + how it's used → reference/
|
|
29
|
+
ADR — architecture decision record → reference/
|
|
30
|
+
Runbook — operational procedure → reference/
|
|
31
|
+
-->
|
|
32
|
+
|
|
33
|
+
## What it is
|
|
34
|
+
|
|
35
|
+
One paragraph: the concept's responsibility and boundary. Graph-grounded.
|
|
36
|
+
|
|
37
|
+
## How it works
|
|
38
|
+
|
|
39
|
+
Primary control/data flow. At least one Mermaid diagram for a significant
|
|
40
|
+
concept (workflow, state, or sequence). Grounded in the call graph.
|
|
41
|
+
|
|
42
|
+
## Used by
|
|
43
|
+
|
|
44
|
+
Cross-links to callers (from `x-callers`). Each link is a relative path to
|
|
45
|
+
another concept page so `okf-validate.sh` can resolve it.
|
|
46
|
+
|
|
47
|
+
## Blast radius
|
|
48
|
+
|
|
49
|
+
What breaks if this changes (from `graph-impact.sh`). Lists `x-grounded-paths`
|
|
50
|
+
so a focused task knows exactly which source files to open.
|
|
51
|
+
|
|
52
|
+
## See also
|
|
53
|
+
|
|
54
|
+
- [Related concept](../systems/other.md)
|