@popoverai/dotrequirements 0.25.0 ā 0.26.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 +8 -1
- package/dist/codebase-to-spec/present.d.ts +9 -0
- package/dist/codebase-to-spec/present.js +23 -2
- package/dist/codebase-to-spec/skill-install.d.ts +16 -6
- package/dist/codebase-to-spec/skill-install.js +109 -47
- package/dist/codebase-to-spec/version-check.d.ts +31 -0
- package/dist/codebase-to-spec/version-check.js +56 -0
- package/dist/commands/ai-setup.d.ts +12 -1
- package/dist/commands/ai-setup.js +65 -33
- package/dist/commands/codebase-to-spec/pack.d.ts +4 -0
- package/dist/commands/codebase-to-spec/pack.js +17 -0
- package/dist/commands/init.js +6 -1
- package/dist/commands/link-resolution.d.ts +79 -0
- package/dist/commands/link-resolution.js +141 -0
- package/dist/commands/link.d.ts +14 -4
- package/dist/commands/link.js +369 -16
- package/dist/commands/pull.js +19 -2
- package/dist/commands/push.js +36 -2
- package/dist/convex.d.ts +5 -3
- package/dist/convex.js +5 -3
- package/dist/harness/cache.d.ts +0 -14
- package/dist/harness/cache.js +1 -41
- package/dist/harness/finalize.js +2 -2
- package/dist/harness/prepare.js +1 -3
- package/dist/harness/requirementsLoader.d.ts +3 -3
- package/dist/harness/requirementsLoader.js +13 -8
- package/dist/mcp/handlers/authoring.d.ts +5 -5
- package/dist/mcp/handlers/authoring.js +9 -9
- package/dist/mcp/handlers/push.d.ts +2 -2
- package/dist/mcp/handlers/push.js +36 -3
- package/dist/mcp/handlers/review.d.ts +4 -4
- package/dist/mcp/handlers/review.js +4 -4
- package/dist/mcp/handlers/search.d.ts +1 -1
- package/dist/mcp/handlers/search.js +1 -1
- package/dist/mcp/index.js +29 -0
- package/dist/push/core.d.ts +18 -0
- package/dist/push/core.js +70 -3
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +1 -1
- package/dist/schema/parser-core.js +5 -1
- package/dist/schema/parser.js +5 -1
- package/dist/schema/run-marker.d.ts +38 -0
- package/dist/schema/run-marker.js +138 -0
- package/dist/schema/schemas.d.ts +12 -0
- package/dist/schema/schemas.js +1 -0
- package/dist/templates/agents/cts-worker.md +1 -1
- package/dist/templates/skills/codebase-to-spec/SKILL.md +33 -11
- package/dist/templates/workflows/specify-codebase.js +4 -2
- package/dist/utils/own-package.d.ts +10 -0
- package/dist/utils/own-package.js +13 -0
- package/dist/utils/project-selector.d.ts +5 -0
- package/dist/utils/project-selector.js +4 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -64,7 +64,13 @@ program
|
|
|
64
64
|
.action(wrapCommand(initCommand));
|
|
65
65
|
program
|
|
66
66
|
.command("link")
|
|
67
|
-
.description("Link local environment to
|
|
67
|
+
.description("Link local environment to a cloud project (creating one when needed)")
|
|
68
|
+
.option("-y, --yes", "Non-interactive: resolve every decision from defaults or flags; never prompt")
|
|
69
|
+
.option("--json", "Machine-readable output (one JSON object on stdout)")
|
|
70
|
+
.option("--team <nameOrId>", "Use this team (implies --yes)")
|
|
71
|
+
.option("--connect <slug>", "Connect to this existing project (implies --yes)")
|
|
72
|
+
.option("--create", "Create a new project (implies --yes)")
|
|
73
|
+
.option("-n, --name <name>", "Project name for --create or rename")
|
|
68
74
|
.action(wrapCommand(linkCommand));
|
|
69
75
|
program
|
|
70
76
|
.command("pull")
|
|
@@ -125,6 +131,7 @@ program
|
|
|
125
131
|
program
|
|
126
132
|
.command("ai-setup")
|
|
127
133
|
.description("Configure MCP server for your AI assistant (Claude Code, Claude Desktop, etc.)")
|
|
134
|
+
.option("-a, --assistant <id>", "Configure for this assistant without prompting (e.g. claude-code)")
|
|
128
135
|
.action(wrapCommand(aiSetupCommand));
|
|
129
136
|
program
|
|
130
137
|
.command("search <query>")
|
|
@@ -85,6 +85,15 @@ export declare function splitComposedSpec(composed: string, outline: Outline): M
|
|
|
85
85
|
* We just read it and return it.
|
|
86
86
|
*/
|
|
87
87
|
export declare function buildSingleFileDoc(composedSpec: string): string;
|
|
88
|
+
/**
|
|
89
|
+
* Insert the run marker as a top-level frontmatter field (IMPORT-1.0).
|
|
90
|
+
*
|
|
91
|
+
* At push time, a valid marker on a newly-created document grants its rows
|
|
92
|
+
* `imported` authorship ā exempt from the requirement limit (IMPORT-2).
|
|
93
|
+
* Returns the content unchanged when it has no frontmatter block (defensive;
|
|
94
|
+
* present output always has one).
|
|
95
|
+
*/
|
|
96
|
+
export declare function stampRunMarker(content: string, marker: string): string;
|
|
88
97
|
/**
|
|
89
98
|
* Build a per-area requirements document for split mode.
|
|
90
99
|
*/
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { dirname, join } from "node:path";
|
|
18
18
|
import { parseRequirementsFile } from "../schema/parser.js";
|
|
19
|
+
import { generateRunMarker } from "../schema/run-marker.js";
|
|
19
20
|
import { sanitizeAreaName } from "./fan-out.js";
|
|
20
21
|
import { promptOverwriteChoice } from "./interactive.js";
|
|
21
22
|
export const AREA_SPLIT_THRESHOLD = 5;
|
|
@@ -113,6 +114,20 @@ export function splitComposedSpec(composed, outline) {
|
|
|
113
114
|
export function buildSingleFileDoc(composedSpec) {
|
|
114
115
|
return composedSpec;
|
|
115
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Insert the run marker as a top-level frontmatter field (IMPORT-1.0).
|
|
119
|
+
*
|
|
120
|
+
* At push time, a valid marker on a newly-created document grants its rows
|
|
121
|
+
* `imported` authorship ā exempt from the requirement limit (IMPORT-2).
|
|
122
|
+
* Returns the content unchanged when it has no frontmatter block (defensive;
|
|
123
|
+
* present output always has one).
|
|
124
|
+
*/
|
|
125
|
+
export function stampRunMarker(content, marker) {
|
|
126
|
+
if (!content.startsWith("---\n")) {
|
|
127
|
+
return content;
|
|
128
|
+
}
|
|
129
|
+
return `---\nctsRun: ${marker}\n${content.slice(4)}`;
|
|
130
|
+
}
|
|
116
131
|
/**
|
|
117
132
|
* Build a per-area requirements document for split mode.
|
|
118
133
|
*/
|
|
@@ -211,11 +226,14 @@ export async function runPresent(options) {
|
|
|
211
226
|
const composedSpec = readFileSync(finalSpecPath, "utf-8");
|
|
212
227
|
const plan = planPresentPaths(outline, projectRoot, outputSubdir);
|
|
213
228
|
// Compute the content for each output up front.
|
|
229
|
+
// IMPORT-1.0/1.1: one run marker per present invocation, stamped into every
|
|
230
|
+
// document this run produces.
|
|
231
|
+
const runMarker = generateRunMarker();
|
|
214
232
|
const planned = [];
|
|
215
233
|
if (plan.mode === "single") {
|
|
216
234
|
planned.push({
|
|
217
235
|
outPath: plan.paths[0].outPath,
|
|
218
|
-
content: buildSingleFileDoc(composedSpec),
|
|
236
|
+
content: stampRunMarker(buildSingleFileDoc(composedSpec), runMarker),
|
|
219
237
|
});
|
|
220
238
|
}
|
|
221
239
|
else {
|
|
@@ -223,7 +241,10 @@ export async function runPresent(options) {
|
|
|
223
241
|
for (const { area, outPath } of plan.paths) {
|
|
224
242
|
const body = sections.get(area) ??
|
|
225
243
|
"_(no content for this area was found in the composed spec)_";
|
|
226
|
-
planned.push({
|
|
244
|
+
planned.push({
|
|
245
|
+
outPath,
|
|
246
|
+
content: stampRunMarker(buildAreaDoc(outline, area, body), runMarker),
|
|
247
|
+
});
|
|
227
248
|
}
|
|
228
249
|
}
|
|
229
250
|
// In fail-fast mode, scan for any conflict before writing anything.
|
|
@@ -59,17 +59,27 @@ export interface SkillInstallResult {
|
|
|
59
59
|
*/
|
|
60
60
|
export declare function resolveSkillDir(options: SkillInstallOptions): string;
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
62
|
+
* Resolve the CLI invocation stamped into the installed artifacts
|
|
63
|
+
* (CTSO-INSTALL-5): a version-pinned `npx` invocation of this very package,
|
|
64
|
+
* so the installed bundle runs without `dotrequirements` on PATH and always
|
|
65
|
+
* shells out to the same CLI version that performed the install.
|
|
63
66
|
*/
|
|
64
|
-
export declare function
|
|
67
|
+
export declare function resolveCliInvocation(): string;
|
|
65
68
|
/**
|
|
66
|
-
* Load the bundled
|
|
69
|
+
* Load the bundled SKILL.md template, rendering `{{DOTREQ_CLI}}` to the given
|
|
70
|
+
* CLI invocation (defaults to the version-pinned npx form).
|
|
67
71
|
*/
|
|
68
|
-
export declare function
|
|
72
|
+
export declare function loadSkillTemplate(cliInvocation?: string): string;
|
|
69
73
|
/**
|
|
70
|
-
* Load the bundled
|
|
74
|
+
* Load the bundled cts-worker agent definition template, rendering
|
|
75
|
+
* `{{DOTREQ_CLI}}` to the given CLI invocation.
|
|
71
76
|
*/
|
|
72
|
-
export declare function
|
|
77
|
+
export declare function loadAgentTemplate(cliInvocation?: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* Load the bundled specify-codebase dynamic-workflow script template,
|
|
80
|
+
* rendering `{{DOTREQ_CLI}}` to the given CLI invocation.
|
|
81
|
+
*/
|
|
82
|
+
export declare function loadWorkflowTemplate(cliInvocation?: string): string;
|
|
73
83
|
/**
|
|
74
84
|
* Install (or refuse to overwrite) the codebase-to-spec skill bundle.
|
|
75
85
|
*
|
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
* - CTS-SKILL-1, CTS-SKILL-5
|
|
18
18
|
* - CTSO-INSTALL-1 (bundles the skill, the cts-worker agent, and the workflow)
|
|
19
19
|
*/
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
20
21
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
22
|
import { homedir } from "node:os";
|
|
22
23
|
import { dirname, join, resolve } from "node:path";
|
|
24
|
+
import { getOwnPackage } from "../utils/own-package.js";
|
|
23
25
|
import { loadTemplate } from "../utils/templates.js";
|
|
24
26
|
/**
|
|
25
27
|
* Resolve the directory in which the skill should live, given the user's
|
|
@@ -45,39 +47,76 @@ export function resolveSkillDir(options) {
|
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
/**
|
|
48
|
-
*
|
|
50
|
+
* Resolve the CLI invocation stamped into the installed artifacts
|
|
51
|
+
* (CTSO-INSTALL-5): a version-pinned `npx` invocation of this very package,
|
|
52
|
+
* so the installed bundle runs without `dotrequirements` on PATH and always
|
|
53
|
+
* shells out to the same CLI version that performed the install.
|
|
49
54
|
*/
|
|
50
|
-
export function
|
|
51
|
-
|
|
55
|
+
export function resolveCliInvocation() {
|
|
56
|
+
const pkg = getOwnPackage();
|
|
57
|
+
return `npx -y ${pkg.name}@${pkg.version}`;
|
|
52
58
|
}
|
|
53
59
|
/**
|
|
54
|
-
* Load the bundled
|
|
60
|
+
* Load the bundled SKILL.md template, rendering `{{DOTREQ_CLI}}` to the given
|
|
61
|
+
* CLI invocation (defaults to the version-pinned npx form).
|
|
55
62
|
*/
|
|
56
|
-
export function
|
|
57
|
-
return loadTemplate("
|
|
63
|
+
export function loadSkillTemplate(cliInvocation = resolveCliInvocation()) {
|
|
64
|
+
return loadTemplate("skills/codebase-to-spec/SKILL.md", {
|
|
65
|
+
DOTREQ_CLI: cliInvocation,
|
|
66
|
+
});
|
|
58
67
|
}
|
|
59
68
|
/**
|
|
60
|
-
* Load the bundled
|
|
69
|
+
* Load the bundled cts-worker agent definition template, rendering
|
|
70
|
+
* `{{DOTREQ_CLI}}` to the given CLI invocation.
|
|
61
71
|
*/
|
|
62
|
-
export function
|
|
63
|
-
return loadTemplate("
|
|
72
|
+
export function loadAgentTemplate(cliInvocation = resolveCliInvocation()) {
|
|
73
|
+
return loadTemplate("agents/cts-worker.md", { DOTREQ_CLI: cliInvocation });
|
|
64
74
|
}
|
|
65
75
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
76
|
+
* Load the bundled specify-codebase dynamic-workflow script template,
|
|
77
|
+
* rendering `{{DOTREQ_CLI}}` to the given CLI invocation.
|
|
68
78
|
*/
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
export function loadWorkflowTemplate(cliInvocation = resolveCliInvocation()) {
|
|
80
|
+
return loadTemplate("workflows/specify-codebase.js", {
|
|
81
|
+
DOTREQ_CLI: cliInvocation,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Manifest written next to the installed SKILL.md, recording the content hash
|
|
86
|
+
* of every file the install wrote. On a later re-run, an on-disk file whose
|
|
87
|
+
* hash still matches its manifest entry is a *stock* install (possibly from an
|
|
88
|
+
* older CLI version) and is safe to refresh without --overwrite; a mismatch
|
|
89
|
+
* means the user hand-edited it (CTSO-INSTALL-6).
|
|
90
|
+
*/
|
|
91
|
+
const MANIFEST_FILENAME = ".install-manifest.json";
|
|
92
|
+
function sha256(content) {
|
|
93
|
+
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
94
|
+
}
|
|
95
|
+
function readManifest(skillDir) {
|
|
96
|
+
const manifestPath = join(skillDir, MANIFEST_FILENAME);
|
|
97
|
+
if (!existsSync(manifestPath))
|
|
98
|
+
return undefined;
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// An unreadable manifest gives no provenance; fall back to treating
|
|
104
|
+
// differing files as hand-edited (the safe direction).
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function classifyPiece(key, description, targetPath, content, manifest) {
|
|
109
|
+
if (!existsSync(targetPath)) {
|
|
110
|
+
return { key, description, targetPath, content, state: "missing" };
|
|
77
111
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
const onDisk = readFileSync(targetPath, "utf-8");
|
|
113
|
+
if (onDisk === content) {
|
|
114
|
+
return { key, description, targetPath, content, state: "current" };
|
|
115
|
+
}
|
|
116
|
+
if (manifest?.files[key] === sha256(onDisk)) {
|
|
117
|
+
return { key, description, targetPath, content, state: "stock" };
|
|
118
|
+
}
|
|
119
|
+
return { key, description, targetPath, content, state: "modified" };
|
|
81
120
|
}
|
|
82
121
|
/**
|
|
83
122
|
* Install (or refuse to overwrite) the codebase-to-spec skill bundle.
|
|
@@ -92,37 +131,60 @@ function installTemplateFile(templateContent, targetPath, overwrite, description
|
|
|
92
131
|
export function installSkill(options = {}) {
|
|
93
132
|
const skillDir = resolveSkillDir(options);
|
|
94
133
|
const skillPath = join(skillDir, "SKILL.md");
|
|
95
|
-
const
|
|
96
|
-
const
|
|
134
|
+
const scope = options.scope ?? "project";
|
|
135
|
+
const cliInvocation = resolveCliInvocation();
|
|
136
|
+
const manifest = readManifest(skillDir);
|
|
137
|
+
// Classify every piece before writing anything, so a hand-edit refusal
|
|
138
|
+
// never leaves a partially refreshed bundle (CTSO-INSTALL-6).
|
|
139
|
+
const pieces = [
|
|
140
|
+
classifyPiece("skill", "SKILL.md", skillPath, loadSkillTemplate(cliInvocation), manifest),
|
|
141
|
+
];
|
|
97
142
|
// A custom target dir is an arbitrary location CC won't read agents or
|
|
98
143
|
// workflows from, so it gets the skill body only. Project and global both
|
|
99
144
|
// install the full bundle under their `.claude/` base.
|
|
100
|
-
|
|
101
|
-
if (scope
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
145
|
+
let companions;
|
|
146
|
+
if (scope !== "custom") {
|
|
147
|
+
const claudeBase = scope === "global"
|
|
148
|
+
? join(options.globalRoot ?? homedir(), ".claude")
|
|
149
|
+
: join(options.projectRoot ?? process.cwd(), ".claude");
|
|
150
|
+
const agentPath = join(claudeBase, "agents", "cts-worker.md");
|
|
151
|
+
const workflowPath = join(claudeBase, "workflows", "specify-codebase.js");
|
|
152
|
+
pieces.push(classifyPiece("agent", "cts-worker agent definition", agentPath, loadAgentTemplate(cliInvocation), manifest),
|
|
153
|
+
// The workflow script is discovered by name from .claude/workflows/;
|
|
154
|
+
// no settings registration needed, unlike a hook.
|
|
155
|
+
classifyPiece("workflow", "specify-codebase workflow script", workflowPath, loadWorkflowTemplate(cliInvocation), manifest));
|
|
156
|
+
companions = { agentPath, workflowPath };
|
|
107
157
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
158
|
+
// Hand-edited files block the whole refresh (named individually) unless
|
|
159
|
+
// --overwrite; unmodified-stock files from any prior version refresh freely.
|
|
160
|
+
if (options.overwrite !== true) {
|
|
161
|
+
const modified = pieces.filter((p) => p.state === "modified");
|
|
162
|
+
if (modified.length > 0) {
|
|
163
|
+
const lines = modified.map((p) => ` ${p.description}: ${p.targetPath}`);
|
|
164
|
+
throw new Error(`Refusing to replace file(s) modified since install (or installed without manifest tracking):\n${lines.join("\n")}\nPass --overwrite to replace, or delete the file(s) first.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const skillPiece = pieces[0];
|
|
168
|
+
for (const piece of pieces) {
|
|
169
|
+
if (piece.state === "current")
|
|
170
|
+
continue;
|
|
171
|
+
mkdirSync(dirname(piece.targetPath), { recursive: true });
|
|
172
|
+
writeFileSync(piece.targetPath, piece.content, "utf-8");
|
|
173
|
+
}
|
|
174
|
+
// Record what this install wrote (or confirmed current) so the next re-run
|
|
175
|
+
// can tell stock content from hand-edits. Also adopts manifest-less installs
|
|
176
|
+
// whose content matches the current templates.
|
|
177
|
+
const manifestOut = {
|
|
178
|
+
version: getOwnPackage().version,
|
|
179
|
+
files: Object.fromEntries(pieces.map((p) => [p.key, sha256(p.content)])),
|
|
180
|
+
};
|
|
181
|
+
mkdirSync(skillDir, { recursive: true });
|
|
182
|
+
writeFileSync(join(skillDir, MANIFEST_FILENAME), `${JSON.stringify(manifestOut, null, 2)}\n`, "utf-8");
|
|
118
183
|
return {
|
|
119
184
|
installedPath: skillPath,
|
|
120
|
-
installed:
|
|
121
|
-
overwrote:
|
|
122
|
-
companions
|
|
123
|
-
agentPath,
|
|
124
|
-
workflowPath,
|
|
125
|
-
},
|
|
185
|
+
installed: skillPiece.state !== "current",
|
|
186
|
+
overwrote: skillPiece.state === "stock" || skillPiece.state === "modified",
|
|
187
|
+
companions,
|
|
126
188
|
};
|
|
127
189
|
}
|
|
128
190
|
//# sourceMappingURL=skill-install.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-soft check for a newer published CLI version (CTS-PACK-4).
|
|
3
|
+
*
|
|
4
|
+
* The skill bundle pins the CLI version it was installed with
|
|
5
|
+
* (CTSO-INSTALL-5), so a long-lived install would otherwise run stale
|
|
6
|
+
* forever without noticing. Pack ā the first deterministic step of every
|
|
7
|
+
* run ā surfaces staleness so the skill can offer a refresh (CTSO-CONV-7).
|
|
8
|
+
*
|
|
9
|
+
* Any failure (offline, slow or unreachable registry, malformed response)
|
|
10
|
+
* yields `undefined`: the check must never fail or delay the pack beyond a
|
|
11
|
+
* short timeout.
|
|
12
|
+
*/
|
|
13
|
+
export interface VersionCheckResult {
|
|
14
|
+
current: string;
|
|
15
|
+
latest: string;
|
|
16
|
+
stale: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare const DEFAULT_VERSION_CHECK_TIMEOUT_MS = 2000;
|
|
19
|
+
/**
|
|
20
|
+
* True when `latest` is a strictly newer release than `current`, comparing
|
|
21
|
+
* numeric segments. Prerelease suffixes are ignored; when the numeric
|
|
22
|
+
* segments tie, the versions are treated as equal (not stale).
|
|
23
|
+
*/
|
|
24
|
+
export declare function isNewerVersion(latest: string, current: string): boolean;
|
|
25
|
+
export interface VersionCheckOptions {
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
/** Injectable for tests. */
|
|
28
|
+
fetchImpl?: typeof fetch;
|
|
29
|
+
}
|
|
30
|
+
export declare function checkForNewerVersion(options?: VersionCheckOptions): Promise<VersionCheckResult | undefined>;
|
|
31
|
+
//# sourceMappingURL=version-check.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-soft check for a newer published CLI version (CTS-PACK-4).
|
|
3
|
+
*
|
|
4
|
+
* The skill bundle pins the CLI version it was installed with
|
|
5
|
+
* (CTSO-INSTALL-5), so a long-lived install would otherwise run stale
|
|
6
|
+
* forever without noticing. Pack ā the first deterministic step of every
|
|
7
|
+
* run ā surfaces staleness so the skill can offer a refresh (CTSO-CONV-7).
|
|
8
|
+
*
|
|
9
|
+
* Any failure (offline, slow or unreachable registry, malformed response)
|
|
10
|
+
* yields `undefined`: the check must never fail or delay the pack beyond a
|
|
11
|
+
* short timeout.
|
|
12
|
+
*/
|
|
13
|
+
import { getOwnPackage } from "../utils/own-package.js";
|
|
14
|
+
export const DEFAULT_VERSION_CHECK_TIMEOUT_MS = 2_000;
|
|
15
|
+
/**
|
|
16
|
+
* True when `latest` is a strictly newer release than `current`, comparing
|
|
17
|
+
* numeric segments. Prerelease suffixes are ignored; when the numeric
|
|
18
|
+
* segments tie, the versions are treated as equal (not stale).
|
|
19
|
+
*/
|
|
20
|
+
export function isNewerVersion(latest, current) {
|
|
21
|
+
const parse = (v) => v
|
|
22
|
+
.split("-")[0]
|
|
23
|
+
.split(".")
|
|
24
|
+
.map((seg) => Number.parseInt(seg, 10) || 0);
|
|
25
|
+
const a = parse(latest);
|
|
26
|
+
const b = parse(current);
|
|
27
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
28
|
+
const diff = (a[i] ?? 0) - (b[i] ?? 0);
|
|
29
|
+
if (diff !== 0)
|
|
30
|
+
return diff > 0;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
export async function checkForNewerVersion(options = {}) {
|
|
35
|
+
try {
|
|
36
|
+
const { name, version } = getOwnPackage();
|
|
37
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
38
|
+
const response = await fetchImpl(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`, {
|
|
39
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_VERSION_CHECK_TIMEOUT_MS),
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok)
|
|
42
|
+
return undefined;
|
|
43
|
+
const body = (await response.json());
|
|
44
|
+
if (typeof body.version !== "string")
|
|
45
|
+
return undefined;
|
|
46
|
+
return {
|
|
47
|
+
current: version,
|
|
48
|
+
latest: body.version,
|
|
49
|
+
stale: isNewerVersion(body.version, version),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=version-check.js.map
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assistant identifiers accepted by the non-interactive --assistant flag
|
|
3
|
+
* (AISETUP-1). "other" is interactive-only: it prompts for a custom path.
|
|
4
|
+
*/
|
|
5
|
+
export declare const SUPPORTED_ASSISTANTS: readonly ["claude-code", "claude-desktop", "cursor", "antigravity", "codex", "github-copilot"];
|
|
6
|
+
export interface AiSetupOptions {
|
|
7
|
+
assistant?: string;
|
|
8
|
+
}
|
|
1
9
|
/**
|
|
2
10
|
* AI setup command - configures the MCP server for AI assistants
|
|
11
|
+
*
|
|
12
|
+
* AISETUP-1: with --assistant <id>, setup runs without any interactive prompt
|
|
13
|
+
* (an AI assistant configuring itself knows which assistant it is).
|
|
3
14
|
*/
|
|
4
|
-
export declare function aiSetupCommand(): Promise<void>;
|
|
15
|
+
export declare function aiSetupCommand(options?: AiSetupOptions): Promise<void>;
|
|
5
16
|
//# sourceMappingURL=ai-setup.d.ts.map
|
|
@@ -13,9 +13,11 @@ function loadContextFileSection() {
|
|
|
13
13
|
return loadTemplate("context-file-section.md");
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Install workflow guidance to the appropriate context file for a platform
|
|
16
|
+
* Install workflow guidance to the appropriate context file for a platform.
|
|
17
|
+
* With `yes` (the non-interactive --assistant path, AISETUP-1.0), both
|
|
18
|
+
* confirmations are auto-accepted ā matching their interactive defaults.
|
|
17
19
|
*/
|
|
18
|
-
async function installContextFileSection(platform) {
|
|
20
|
+
async function installContextFileSection(platform, yes = false) {
|
|
19
21
|
const fileName = getContextFileName(platform);
|
|
20
22
|
if (!fileName) {
|
|
21
23
|
console.error(`\nā Unknown platform: ${platform}`);
|
|
@@ -37,24 +39,26 @@ async function installContextFileSection(platform) {
|
|
|
37
39
|
existingSection = section.content;
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
|
-
// Prompt user
|
|
42
|
+
// Prompt user (auto-accepted in non-interactive mode)
|
|
41
43
|
const action = existingSection
|
|
42
44
|
? "Update"
|
|
43
45
|
: existsSync(contextFilePath)
|
|
44
46
|
? "Add to"
|
|
45
47
|
: "Create";
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
if (!yes) {
|
|
49
|
+
const response = await prompts({
|
|
50
|
+
type: "confirm",
|
|
51
|
+
name: "install",
|
|
52
|
+
message: `${action} ${fileName} with dotrequirements workflow guidance?`,
|
|
53
|
+
initial: true,
|
|
54
|
+
});
|
|
55
|
+
if (!response.install) {
|
|
56
|
+
console.log("");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
|
-
// If updating, show what will change
|
|
57
|
-
if (existingSection) {
|
|
60
|
+
// If updating, show what will change (auto-accepted in non-interactive mode)
|
|
61
|
+
if (existingSection && !yes) {
|
|
58
62
|
const confirmUpdate = await prompts({
|
|
59
63
|
type: "confirm",
|
|
60
64
|
name: "confirm",
|
|
@@ -80,12 +84,37 @@ async function installContextFileSection(platform) {
|
|
|
80
84
|
console.log(` Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
81
85
|
}
|
|
82
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Assistant identifiers accepted by the non-interactive --assistant flag
|
|
89
|
+
* (AISETUP-1). "other" is interactive-only: it prompts for a custom path.
|
|
90
|
+
*/
|
|
91
|
+
export const SUPPORTED_ASSISTANTS = [
|
|
92
|
+
"claude-code",
|
|
93
|
+
"claude-desktop",
|
|
94
|
+
"cursor",
|
|
95
|
+
"antigravity",
|
|
96
|
+
"codex",
|
|
97
|
+
"github-copilot",
|
|
98
|
+
];
|
|
83
99
|
/**
|
|
84
100
|
* AI setup command - configures the MCP server for AI assistants
|
|
101
|
+
*
|
|
102
|
+
* AISETUP-1: with --assistant <id>, setup runs without any interactive prompt
|
|
103
|
+
* (an AI assistant configuring itself knows which assistant it is).
|
|
85
104
|
*/
|
|
86
|
-
export async function aiSetupCommand() {
|
|
105
|
+
export async function aiSetupCommand(options = {}) {
|
|
87
106
|
console.log(`š§ Setting up ${brand} MCP server\n`);
|
|
88
|
-
|
|
107
|
+
if (options.assistant) {
|
|
108
|
+
const id = options.assistant.toLowerCase();
|
|
109
|
+
if (!SUPPORTED_ASSISTANTS.includes(id)) {
|
|
110
|
+
// AISETUP-1.1: unsupported value ā error listing supported identifiers
|
|
111
|
+
throw new Error(`Unsupported assistant "${options.assistant}". Supported: ${SUPPORTED_ASSISTANTS.join(", ")}`);
|
|
112
|
+
}
|
|
113
|
+
// AISETUP-1.0: no prompt; same setup path as the interactive selector
|
|
114
|
+
await dispatchSetup(id, true);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// AISETUP-1.2: no flag ā interactive selector, unchanged
|
|
89
118
|
const response = await prompts({
|
|
90
119
|
type: "select",
|
|
91
120
|
name: "client",
|
|
@@ -105,34 +134,37 @@ export async function aiSetupCommand() {
|
|
|
105
134
|
console.log("\nSetup cancelled.");
|
|
106
135
|
return;
|
|
107
136
|
}
|
|
108
|
-
|
|
137
|
+
await dispatchSetup(response.client);
|
|
138
|
+
}
|
|
139
|
+
async function dispatchSetup(client, yes = false) {
|
|
140
|
+
switch (client) {
|
|
109
141
|
case "claude-code":
|
|
110
|
-
await setupClaudeCode();
|
|
142
|
+
await setupClaudeCode(yes);
|
|
111
143
|
break;
|
|
112
144
|
case "claude-desktop":
|
|
113
145
|
await setupClaudeDesktop();
|
|
114
146
|
break;
|
|
115
147
|
case "cursor":
|
|
116
|
-
await setupCursor();
|
|
148
|
+
await setupCursor(yes);
|
|
117
149
|
break;
|
|
118
150
|
case "antigravity":
|
|
119
|
-
await setupAntigravity();
|
|
151
|
+
await setupAntigravity(yes);
|
|
120
152
|
break;
|
|
121
153
|
case "codex":
|
|
122
|
-
await setupCodex();
|
|
154
|
+
await setupCodex(yes);
|
|
123
155
|
break;
|
|
124
156
|
case "github-copilot":
|
|
125
|
-
await setupGitHubCopilot();
|
|
157
|
+
await setupGitHubCopilot(yes);
|
|
126
158
|
break;
|
|
127
159
|
case "other":
|
|
128
160
|
await setupOther();
|
|
129
161
|
break;
|
|
130
162
|
default:
|
|
131
|
-
console.log(`\nā ${
|
|
163
|
+
console.log(`\nā ${client} is not yet supported.`);
|
|
132
164
|
console.log("Please refer to the documentation for manual setup.");
|
|
133
165
|
}
|
|
134
166
|
}
|
|
135
|
-
async function setupClaudeCode() {
|
|
167
|
+
async function setupClaudeCode(yes = false) {
|
|
136
168
|
console.log("\nš¦ Configuring MCP for Claude Code...\n");
|
|
137
169
|
const command = `claude mcp add-json dotrequirements '{"type":"stdio","command":"npx","args":["-y","@popoverai/dotrequirements","mcp"]}'`;
|
|
138
170
|
let mcpConfigured = false;
|
|
@@ -162,7 +194,7 @@ async function setupClaudeCode() {
|
|
|
162
194
|
if (mcpConfigured) {
|
|
163
195
|
console.log("\nā ļø Important: Restart Claude Code for changes to take effect.");
|
|
164
196
|
console.log("\nYou can now ask your AI assistant to help write requirements!\n");
|
|
165
|
-
await installContextFileSection("claude-code");
|
|
197
|
+
await installContextFileSection("claude-code", yes);
|
|
166
198
|
}
|
|
167
199
|
}
|
|
168
200
|
async function setupClaudeDesktop() {
|
|
@@ -237,7 +269,7 @@ async function setupClaudeDesktop() {
|
|
|
237
269
|
console.log(JSON.stringify(manualConfig, null, 2));
|
|
238
270
|
}
|
|
239
271
|
}
|
|
240
|
-
async function setupCursor() {
|
|
272
|
+
async function setupCursor(yes = false) {
|
|
241
273
|
console.log("\nš¦ Configuring MCP for Cursor (project-specific)...\n");
|
|
242
274
|
const configPath = join(process.cwd(), ".cursor", "mcp.json");
|
|
243
275
|
const configDir = join(process.cwd(), ".cursor");
|
|
@@ -272,7 +304,7 @@ async function setupCursor() {
|
|
|
272
304
|
console.log(` Config file: ${configPath}\n`);
|
|
273
305
|
console.log("ā ļø Important: Reload or restart Cursor for changes to take effect.");
|
|
274
306
|
console.log("\nYou can now ask your AI assistant to help write requirements!\n");
|
|
275
|
-
await installContextFileSection("cursor");
|
|
307
|
+
await installContextFileSection("cursor", yes);
|
|
276
308
|
}
|
|
277
309
|
catch (error) {
|
|
278
310
|
console.error("ā Failed to configure MCP automatically.");
|
|
@@ -288,7 +320,7 @@ async function setupCursor() {
|
|
|
288
320
|
console.log(JSON.stringify(manualConfig, null, 2));
|
|
289
321
|
}
|
|
290
322
|
}
|
|
291
|
-
async function setupAntigravity() {
|
|
323
|
+
async function setupAntigravity(yes = false) {
|
|
292
324
|
console.log("\nš¦ Configuring MCP for Google Antigravity...\n");
|
|
293
325
|
const configPath = join(homedir(), ".gemini", "antigravity", "mcp_config.json");
|
|
294
326
|
const configDir = join(homedir(), ".gemini", "antigravity");
|
|
@@ -367,7 +399,7 @@ async function setupAntigravity() {
|
|
|
367
399
|
else {
|
|
368
400
|
console.log("");
|
|
369
401
|
}
|
|
370
|
-
await installContextFileSection("antigravity");
|
|
402
|
+
await installContextFileSection("antigravity", yes);
|
|
371
403
|
}
|
|
372
404
|
catch (error) {
|
|
373
405
|
console.error("ā Failed to configure MCP automatically.");
|
|
@@ -376,7 +408,7 @@ async function setupAntigravity() {
|
|
|
376
408
|
console.log(` ${configPath}`);
|
|
377
409
|
}
|
|
378
410
|
}
|
|
379
|
-
async function setupCodex() {
|
|
411
|
+
async function setupCodex(yes = false) {
|
|
380
412
|
console.log("\nš¦ Configuring MCP for OpenAI Codex...\n");
|
|
381
413
|
const command = `codex mcp add dotrequirements -- npx -y @popoverai/dotrequirements mcp`;
|
|
382
414
|
try {
|
|
@@ -384,7 +416,7 @@ async function setupCodex() {
|
|
|
384
416
|
console.log("\nā
MCP configured successfully for Codex!");
|
|
385
417
|
console.log("\nā ļø Important: Restart Codex for changes to take effect.");
|
|
386
418
|
console.log("\nYou can now ask your AI assistant to help write requirements!\n");
|
|
387
|
-
await installContextFileSection("codex");
|
|
419
|
+
await installContextFileSection("codex", yes);
|
|
388
420
|
}
|
|
389
421
|
catch {
|
|
390
422
|
console.error("\nā Failed to configure MCP automatically.");
|
|
@@ -396,12 +428,12 @@ async function setupCodex() {
|
|
|
396
428
|
console.log('args = ["-y", "@popoverai/dotrequirements", "mcp"]');
|
|
397
429
|
}
|
|
398
430
|
}
|
|
399
|
-
async function setupGitHubCopilot() {
|
|
431
|
+
async function setupGitHubCopilot(yes = false) {
|
|
400
432
|
console.log("\nš¦ Configuring skills for GitHub Copilot...\n");
|
|
401
433
|
console.log("ā¹ļø GitHub Copilot discovers MCP servers from your IDE configuration.");
|
|
402
434
|
console.log(" Please configure the MCP server in VS Code settings or GitHub settings.\n");
|
|
403
435
|
console.log(" MCP server command: npx -y @popoverai/dotrequirements mcp\n");
|
|
404
|
-
await installContextFileSection("github-copilot");
|
|
436
|
+
await installContextFileSection("github-copilot", yes);
|
|
405
437
|
}
|
|
406
438
|
async function setupOther() {
|
|
407
439
|
console.log("\nš¦ Custom Context File Setup\n");
|