reffy-cli 0.6.6 → 0.7.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/dist/cli.js +39 -20
- package/dist/doctor.js +10 -6
- package/dist/refs-paths.d.ts +5 -0
- package/dist/refs-paths.js +32 -0
- package/dist/storage.js +2 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import { existsSync, promises as fs, statSync } from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { renderDiagram } from "./diagram.js";
|
|
5
6
|
import { runDoctor } from "./doctor.js";
|
|
7
|
+
import { DEFAULT_REFS_DIRNAME, looksLikeRefsDir, resolveRefsDirName } from "./refs-paths.js";
|
|
6
8
|
import { ReferencesStore } from "./storage.js";
|
|
7
9
|
import { summarizeArtifacts } from "./summarize.js";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version: packageVersion } = require("../package.json");
|
|
8
12
|
const REFFY_ASCII = [
|
|
9
13
|
" __ __ ",
|
|
10
14
|
" _ __ ___ / _|/ _|_ _",
|
|
@@ -13,33 +17,35 @@ const REFFY_ASCII = [
|
|
|
13
17
|
"|_| \\___||_| |_| \\__, |",
|
|
14
18
|
" |___/ ",
|
|
15
19
|
].join("\n");
|
|
16
|
-
|
|
20
|
+
function buildReffyBlock(refsDirName) {
|
|
21
|
+
return `<!-- REFFY:START -->
|
|
17
22
|
# Reffy Instructions
|
|
18
23
|
|
|
19
24
|
These instructions are for AI assistants working in this project.
|
|
20
25
|
|
|
21
|
-
Always open
|
|
26
|
+
Always open \`@/${refsDirName}/AGENTS.md\` when the request:
|
|
22
27
|
- Mentions early-stage ideation, exploration, brainstorming, or raw notes
|
|
23
28
|
- Needs context before drafting specs or proposals
|
|
24
29
|
- Refers to "reffy", "references", "explore", or "context layer"
|
|
25
30
|
|
|
26
|
-
Use
|
|
31
|
+
Use \`@/${refsDirName}/AGENTS.md\` to learn:
|
|
27
32
|
- Reffy workflow and artifact conventions
|
|
28
33
|
- How Reffy and OpenSpec should be sequenced
|
|
29
|
-
- How to store and consume ideation context in
|
|
34
|
+
- How to store and consume ideation context in \`${refsDirName}/\`
|
|
30
35
|
|
|
31
36
|
Keep this managed block so \`reffy init\` can refresh the instructions.
|
|
32
37
|
|
|
33
38
|
<!-- REFFY:END -->`;
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
}
|
|
40
|
+
function buildReffyAgentsContent(refsDirName) {
|
|
41
|
+
return `# Reffy Instructions
|
|
36
42
|
|
|
37
43
|
These instructions are for AI assistants working in this project.
|
|
38
44
|
|
|
39
45
|
## TL;DR Checklist
|
|
40
46
|
|
|
41
47
|
- Decide whether Reffy ideation is needed for this request.
|
|
42
|
-
- If needed, read existing context in
|
|
48
|
+
- If needed, read existing context in \`${refsDirName}/artifacts/\`.
|
|
43
49
|
- Add/update exploratory artifacts and keep them concise.
|
|
44
50
|
- Run \`reffy reindex\` and \`reffy validate\` after artifact changes.
|
|
45
51
|
- After ideation approval, run \`reffy summarize --output json\` and pick only directly relevant artifacts for proposal citations.
|
|
@@ -60,9 +66,9 @@ You can skip Reffy when the request is:
|
|
|
60
66
|
|
|
61
67
|
## Reffy Workflow
|
|
62
68
|
|
|
63
|
-
1. Read existing artifacts in
|
|
69
|
+
1. Read existing artifacts in \`${refsDirName}/artifacts/\`.
|
|
64
70
|
2. Add or update artifacts to capture exploratory context.
|
|
65
|
-
3. Run \`reffy reindex\` to index newly added files into
|
|
71
|
+
3. Run \`reffy reindex\` to index newly added files into \`${refsDirName}/manifest.json\`.
|
|
66
72
|
4. Run \`reffy validate\` to verify manifest contract compliance.
|
|
67
73
|
|
|
68
74
|
## Relationship To OpenSpec
|
|
@@ -102,30 +108,36 @@ No Reffy references used.
|
|
|
102
108
|
|
|
103
109
|
## Artifact Conventions
|
|
104
110
|
|
|
105
|
-
- Treat
|
|
111
|
+
- Treat \`${refsDirName}/\` as a repository-local guidance and ideation context layer.
|
|
106
112
|
- Keep artifact names clear and stable.
|
|
107
113
|
- Prefer markdown notes for exploratory content.
|
|
108
114
|
- Keep manifests machine-readable and schema-compliant (version 1).
|
|
109
115
|
`;
|
|
116
|
+
}
|
|
110
117
|
const REFFY_START = "<!-- REFFY:START -->";
|
|
111
118
|
const REFFY_END = "<!-- REFFY:END -->";
|
|
112
119
|
const OPENSPEC_START = "<!-- OPENSPEC:START -->";
|
|
113
120
|
function upsertReffyBlock(content) {
|
|
121
|
+
return upsertReffyBlockForDir(content, DEFAULT_REFS_DIRNAME);
|
|
122
|
+
}
|
|
123
|
+
function upsertReffyBlockForDir(content, refsDirName) {
|
|
124
|
+
const reffyBlock = buildReffyBlock(refsDirName);
|
|
114
125
|
if (content.includes(REFFY_START) && content.includes(REFFY_END)) {
|
|
115
126
|
const prefix = content.split(REFFY_START)[0] ?? "";
|
|
116
127
|
const suffix = content.split(REFFY_END, 2)[1] ?? "";
|
|
117
128
|
const trimmedSuffix = suffix.trimStart();
|
|
118
|
-
return trimmedSuffix.length > 0 ? `${prefix}${
|
|
129
|
+
return trimmedSuffix.length > 0 ? `${prefix}${reffyBlock}\n\n${trimmedSuffix}` : `${prefix}${reffyBlock}\n`;
|
|
119
130
|
}
|
|
120
131
|
if (content.includes(OPENSPEC_START)) {
|
|
121
132
|
const [before, after] = content.split(OPENSPEC_START, 2);
|
|
122
|
-
return `${before.trimEnd()}\n\n${
|
|
133
|
+
return `${before.trimEnd()}\n\n${reffyBlock}\n\n${OPENSPEC_START}${after}`;
|
|
123
134
|
}
|
|
124
|
-
return content.trim().length > 0 ? `${
|
|
135
|
+
return content.trim().length > 0 ? `${reffyBlock}\n\n${content.trimStart()}` : `${reffyBlock}\n`;
|
|
125
136
|
}
|
|
126
137
|
async function initAgents(repoRoot) {
|
|
138
|
+
const refsDirName = resolveRefsDirName(repoRoot);
|
|
127
139
|
const agentsPath = path.join(repoRoot, "AGENTS.md");
|
|
128
|
-
const reffyAgentsPath = path.join(repoRoot,
|
|
140
|
+
const reffyAgentsPath = path.join(repoRoot, refsDirName, "AGENTS.md");
|
|
129
141
|
let content = "";
|
|
130
142
|
try {
|
|
131
143
|
content = await fs.readFile(agentsPath, "utf8");
|
|
@@ -133,10 +145,10 @@ async function initAgents(repoRoot) {
|
|
|
133
145
|
catch {
|
|
134
146
|
content = "";
|
|
135
147
|
}
|
|
136
|
-
const updated =
|
|
148
|
+
const updated = upsertReffyBlockForDir(content, refsDirName);
|
|
137
149
|
await fs.mkdir(path.dirname(reffyAgentsPath), { recursive: true });
|
|
138
150
|
await fs.writeFile(agentsPath, updated, "utf8");
|
|
139
|
-
await fs.writeFile(reffyAgentsPath,
|
|
151
|
+
await fs.writeFile(reffyAgentsPath, buildReffyAgentsContent(refsDirName), "utf8");
|
|
140
152
|
return { root_agents_path: agentsPath, reffy_agents_path: reffyAgentsPath };
|
|
141
153
|
}
|
|
142
154
|
function pathExists(targetPath) {
|
|
@@ -153,10 +165,10 @@ function isDirectory(targetPath) {
|
|
|
153
165
|
function discoverRepoRoot(startDir) {
|
|
154
166
|
let current = path.resolve(startDir);
|
|
155
167
|
while (true) {
|
|
156
|
-
if (
|
|
168
|
+
if (looksLikeRefsDir(current)) {
|
|
157
169
|
return path.dirname(current);
|
|
158
170
|
}
|
|
159
|
-
if (isDirectory(path.join(current, ".
|
|
171
|
+
if (isDirectory(path.join(current, DEFAULT_REFS_DIRNAME)) || isDirectory(path.join(current, ".references"))) {
|
|
160
172
|
return current;
|
|
161
173
|
}
|
|
162
174
|
if (pathExists(path.join(current, "AGENTS.md")) || pathExists(path.join(current, ".git"))) {
|
|
@@ -224,6 +236,9 @@ function usage() {
|
|
|
224
236
|
return [
|
|
225
237
|
"Usage: reffy <command> [--repo PATH] [--output text|json]",
|
|
226
238
|
"",
|
|
239
|
+
"Flags:",
|
|
240
|
+
" --version Print the installed reffy package version.",
|
|
241
|
+
"",
|
|
227
242
|
"Commands:",
|
|
228
243
|
" init Ensure root AGENTS.md block and .reffy/AGENTS.md are up to date.",
|
|
229
244
|
" bootstrap Run init, ensure .reffy structure exists, then reindex artifacts.",
|
|
@@ -365,9 +380,13 @@ function printSection(title, values) {
|
|
|
365
380
|
}
|
|
366
381
|
async function main() {
|
|
367
382
|
const [, , command, ...rest] = process.argv;
|
|
368
|
-
if (!command) {
|
|
383
|
+
if (!command || command === "--help" || command === "-h") {
|
|
369
384
|
console.error(usage());
|
|
370
|
-
return 1;
|
|
385
|
+
return command ? 0 : 1;
|
|
386
|
+
}
|
|
387
|
+
if (command === "--version") {
|
|
388
|
+
console.log(packageVersion);
|
|
389
|
+
return 0;
|
|
371
390
|
}
|
|
372
391
|
if (command === "diagram") {
|
|
373
392
|
const [subcommand, ...diagramArgs] = rest;
|
package/dist/doctor.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { promises as fs } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { validateManifest } from "./manifest.js";
|
|
5
|
+
import { resolveRefsDirName } from "./refs-paths.js";
|
|
5
6
|
async function pathExists(targetPath) {
|
|
6
7
|
try {
|
|
7
8
|
await fs.access(targetPath);
|
|
@@ -27,7 +28,8 @@ function summarizeChecks(checks) {
|
|
|
27
28
|
}
|
|
28
29
|
export async function runDoctor(repoRoot, options) {
|
|
29
30
|
const checks = [];
|
|
30
|
-
const
|
|
31
|
+
const refsDirName = resolveRefsDirName(repoRoot);
|
|
32
|
+
const refsDir = path.join(repoRoot, refsDirName);
|
|
31
33
|
const artifactsDir = path.join(refsDir, "artifacts");
|
|
32
34
|
const manifestPath = path.join(refsDir, "manifest.json");
|
|
33
35
|
const rootAgentsPath = path.join(repoRoot, "AGENTS.md");
|
|
@@ -37,14 +39,16 @@ export async function runDoctor(repoRoot, options) {
|
|
|
37
39
|
id: "refs_dir_exists",
|
|
38
40
|
level: "required",
|
|
39
41
|
ok: refsDirExists,
|
|
40
|
-
message: refsDirExists ?
|
|
42
|
+
message: refsDirExists ? `${refsDirName} directory found` : `${refsDirName} directory is missing`,
|
|
41
43
|
});
|
|
42
44
|
const artifactsDirExists = await pathExists(artifactsDir);
|
|
43
45
|
checks.push({
|
|
44
46
|
id: "artifacts_dir_exists",
|
|
45
47
|
level: "required",
|
|
46
48
|
ok: artifactsDirExists,
|
|
47
|
-
message: artifactsDirExists
|
|
49
|
+
message: artifactsDirExists
|
|
50
|
+
? `${refsDirName}/artifacts directory found`
|
|
51
|
+
: `${refsDirName}/artifacts directory is missing`,
|
|
48
52
|
});
|
|
49
53
|
const rootAgentsExists = await pathExists(rootAgentsPath);
|
|
50
54
|
checks.push({
|
|
@@ -58,7 +62,7 @@ export async function runDoctor(repoRoot, options) {
|
|
|
58
62
|
id: "refs_agents_exists",
|
|
59
63
|
level: "required",
|
|
60
64
|
ok: refsAgentsExists,
|
|
61
|
-
message: refsAgentsExists ?
|
|
65
|
+
message: refsAgentsExists ? `${refsDirName}/AGENTS.md found` : `${refsDirName}/AGENTS.md is missing`,
|
|
62
66
|
});
|
|
63
67
|
const manifestExists = await pathExists(manifestPath);
|
|
64
68
|
if (!manifestExists) {
|
|
@@ -66,7 +70,7 @@ export async function runDoctor(repoRoot, options) {
|
|
|
66
70
|
id: "manifest_valid",
|
|
67
71
|
level: "required",
|
|
68
72
|
ok: false,
|
|
69
|
-
message:
|
|
73
|
+
message: `${refsDirName}/manifest.json is missing`,
|
|
70
74
|
});
|
|
71
75
|
}
|
|
72
76
|
else {
|
|
@@ -76,7 +80,7 @@ export async function runDoctor(repoRoot, options) {
|
|
|
76
80
|
level: "required",
|
|
77
81
|
ok: manifestResult.ok,
|
|
78
82
|
message: manifestResult.ok
|
|
79
|
-
?
|
|
83
|
+
? `${refsDirName}/manifest.json is valid (artifacts=${String(manifestResult.artifact_count)})`
|
|
80
84
|
: `manifest invalid: ${manifestResult.errors.join("; ")}`,
|
|
81
85
|
});
|
|
82
86
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const DEFAULT_REFS_DIRNAME = ".reffy";
|
|
2
|
+
export declare const LEGACY_REFS_DIRNAME = ".references";
|
|
3
|
+
export declare function resolveRefsDirName(repoRoot: string): string;
|
|
4
|
+
export declare function resolveRefsDir(repoRoot: string): string;
|
|
5
|
+
export declare function looksLikeRefsDir(targetPath: string): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const DEFAULT_REFS_DIRNAME = ".reffy";
|
|
4
|
+
export const LEGACY_REFS_DIRNAME = ".references";
|
|
5
|
+
function isDirectory(targetPath) {
|
|
6
|
+
try {
|
|
7
|
+
return statSync(targetPath).isDirectory();
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function resolveRefsDirName(repoRoot) {
|
|
14
|
+
const reffyDir = path.join(repoRoot, DEFAULT_REFS_DIRNAME);
|
|
15
|
+
if (isDirectory(reffyDir)) {
|
|
16
|
+
return DEFAULT_REFS_DIRNAME;
|
|
17
|
+
}
|
|
18
|
+
const legacyDir = path.join(repoRoot, LEGACY_REFS_DIRNAME);
|
|
19
|
+
if (isDirectory(legacyDir)) {
|
|
20
|
+
return LEGACY_REFS_DIRNAME;
|
|
21
|
+
}
|
|
22
|
+
return DEFAULT_REFS_DIRNAME;
|
|
23
|
+
}
|
|
24
|
+
export function resolveRefsDir(repoRoot) {
|
|
25
|
+
return path.join(repoRoot, resolveRefsDirName(repoRoot));
|
|
26
|
+
}
|
|
27
|
+
export function looksLikeRefsDir(targetPath) {
|
|
28
|
+
const base = path.basename(targetPath);
|
|
29
|
+
return ((base === DEFAULT_REFS_DIRNAME || base === LEGACY_REFS_DIRNAME) &&
|
|
30
|
+
existsSync(path.join(targetPath, "artifacts")) &&
|
|
31
|
+
isDirectory(path.join(targetPath, "artifacts")));
|
|
32
|
+
}
|
package/dist/storage.js
CHANGED
|
@@ -3,6 +3,7 @@ import { mkdirSync } from "node:fs";
|
|
|
3
3
|
import { promises as fs } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { inferArtifactType, MANIFEST_VERSION, validateManifest } from "./manifest.js";
|
|
6
|
+
import { resolveRefsDir } from "./refs-paths.js";
|
|
6
7
|
function utcNow() {
|
|
7
8
|
return new Date().toISOString();
|
|
8
9
|
}
|
|
@@ -16,7 +17,7 @@ export class ReferencesStore {
|
|
|
16
17
|
manifestPath;
|
|
17
18
|
constructor(repoRoot) {
|
|
18
19
|
this.repoRoot = repoRoot;
|
|
19
|
-
this.refsDir =
|
|
20
|
+
this.refsDir = resolveRefsDir(repoRoot);
|
|
20
21
|
this.artifactsDir = path.join(this.refsDir, "artifacts");
|
|
21
22
|
this.manifestPath = path.join(this.refsDir, "manifest.json");
|
|
22
23
|
this.ensureStructure();
|