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 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
- const REFFY_BLOCK = `<!-- REFFY:START -->
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 \`@/.reffy/AGENTS.md\` when the request:
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 \`@/.reffy/AGENTS.md\` to learn:
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 \`.reffy/\`
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
- const REFFY_AGENTS_RELATIVE = path.join(".reffy", "AGENTS.md");
35
- const REFFY_AGENTS_CONTENT = `# Reffy Instructions
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 \`.reffy/artifacts/\`.
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 \`.reffy/artifacts/\`.
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 \`.reffy/manifest.json\`.
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 \`.reffy/\` as a repository-local guidance and ideation context layer.
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}${REFFY_BLOCK}\n\n${trimmedSuffix}` : `${prefix}${REFFY_BLOCK}\n`;
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${REFFY_BLOCK}\n\n${OPENSPEC_START}${after}`;
133
+ return `${before.trimEnd()}\n\n${reffyBlock}\n\n${OPENSPEC_START}${after}`;
123
134
  }
124
- return content.trim().length > 0 ? `${REFFY_BLOCK}\n\n${content.trimStart()}` : `${REFFY_BLOCK}\n`;
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, REFFY_AGENTS_RELATIVE);
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 = upsertReffyBlock(content);
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, REFFY_AGENTS_CONTENT, "utf8");
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 (path.basename(current) === ".reffy" && isDirectory(path.join(current, "artifacts"))) {
168
+ if (looksLikeRefsDir(current)) {
157
169
  return path.dirname(current);
158
170
  }
159
- if (isDirectory(path.join(current, ".reffy"))) {
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 refsDir = path.join(repoRoot, ".reffy");
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 ? ".reffy directory found" : ".reffy directory is missing",
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 ? ".reffy/artifacts directory found" : ".reffy/artifacts directory is missing",
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 ? ".reffy/AGENTS.md found" : ".reffy/AGENTS.md is missing",
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: ".reffy/manifest.json is missing",
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
- ? `.reffy/manifest.json is valid (artifacts=${String(manifestResult.artifact_count)})`
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 = path.join(repoRoot, ".reffy");
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reffy-cli",
3
- "version": "0.6.6",
3
+ "version": "0.7.0",
4
4
  "description": "CLI-first, framework-agnostic references workflow for any repo (TypeScript)",
5
5
  "keywords": [
6
6
  "reffy",