@primitive.ai/prim 0.1.0-alpha.19 → 0.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -37
- package/SKILL.md +40 -183
- package/dist/{chunk-6SIEWWUL.js → chunk-26VA3ADF.js} +1 -3
- package/dist/{chunk-TPQ3X244.js → chunk-E5UZXMZL.js} +2 -31
- package/dist/daemon/server.js +1 -1
- package/dist/hooks/post-tool-use.js +1 -1
- package/dist/hooks/pre-commit.js +4 -157
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/index.js +22 -431
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -7,9 +7,8 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
checkAffectedDecisions,
|
|
9
9
|
daemonOrDirectGet,
|
|
10
|
-
formatDecisionsWarning
|
|
11
|
-
|
|
12
|
-
} from "./chunk-TPQ3X244.js";
|
|
10
|
+
formatDecisionsWarning
|
|
11
|
+
} from "./chunk-E5UZXMZL.js";
|
|
13
12
|
import {
|
|
14
13
|
HttpError,
|
|
15
14
|
REFRESH_TOKEN_PATH,
|
|
@@ -20,7 +19,7 @@ import {
|
|
|
20
19
|
getSiteUrl,
|
|
21
20
|
getTokenExpiresAt,
|
|
22
21
|
saveTokenExpiry
|
|
23
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-26VA3ADF.js";
|
|
24
23
|
import {
|
|
25
24
|
JOURNAL_DIR,
|
|
26
25
|
SESSIONS_DIR,
|
|
@@ -34,7 +33,7 @@ import {
|
|
|
34
33
|
} from "./chunk-UTKQTZHL.js";
|
|
35
34
|
|
|
36
35
|
// src/index.ts
|
|
37
|
-
import { readFileSync as
|
|
36
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
38
37
|
import { dirname as dirname6, resolve as resolve4 } from "path";
|
|
39
38
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
40
39
|
import { Command } from "commander";
|
|
@@ -747,126 +746,9 @@ ${line("project", result.project)}`);
|
|
|
747
746
|
});
|
|
748
747
|
}
|
|
749
748
|
|
|
750
|
-
// src/commands/context.ts
|
|
751
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
752
|
-
function registerContextCommands(program2) {
|
|
753
|
-
const context = program2.command("context").description("Manage contexts");
|
|
754
|
-
context.command("list").description("List contexts").option("-s, --scope <scope>", "Filter by scope: project, global, external").option("-t, --project-id <projectId>", "List contexts linked to a specific project").option("--json", "Output as JSON").action(async (opts) => {
|
|
755
|
-
const client = getClient();
|
|
756
|
-
const params = new URLSearchParams();
|
|
757
|
-
if (opts.projectId) {
|
|
758
|
-
params.set("taskId", opts.projectId);
|
|
759
|
-
}
|
|
760
|
-
if (opts.scope) {
|
|
761
|
-
params.set("scope", opts.scope === "project" ? "task" : opts.scope);
|
|
762
|
-
}
|
|
763
|
-
const contexts = await client.get(`/api/cli/contexts?${params.toString()}`);
|
|
764
|
-
if (opts.json) {
|
|
765
|
-
printJson(contexts);
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
printContextList(contexts);
|
|
769
|
-
});
|
|
770
|
-
context.command("get <contextId>").description("Get a context by ID").option("--json", "Output as JSON (default behavior; accepted for symmetry)").action(async (contextId) => {
|
|
771
|
-
const client = getClient();
|
|
772
|
-
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
773
|
-
printJson(ctx);
|
|
774
|
-
});
|
|
775
|
-
context.command("create").description("Create a new context").requiredOption("-s, --scope <scope>", "Scope: project, global, external").requiredOption("-n, --name <name>", "Context name").option("-t, --text <text>", "Context text content").option("-f, --file <path>", "Read text content from file").option("--project-id <projectId>", "Link to project(s), comma-separated").option("--spec", "Mark as a spec document").option("--json", "Output as JSON").action(
|
|
776
|
-
async (opts) => {
|
|
777
|
-
const client = getClient();
|
|
778
|
-
let text = opts.text;
|
|
779
|
-
if (opts.file) {
|
|
780
|
-
text = readFileSync4(opts.file, "utf-8");
|
|
781
|
-
}
|
|
782
|
-
const taskIds = opts.projectId ? opts.projectId.split(",").map((id) => id.trim()) : void 0;
|
|
783
|
-
const result = await client.post("/api/cli/contexts", {
|
|
784
|
-
scope: opts.scope === "project" ? "task" : opts.scope,
|
|
785
|
-
name: opts.name,
|
|
786
|
-
text,
|
|
787
|
-
taskIds,
|
|
788
|
-
isSpecDocument: opts.spec ?? false
|
|
789
|
-
});
|
|
790
|
-
if (opts.json) {
|
|
791
|
-
printJson({ _id: result._id });
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
console.error(`Created context: ${result._id}`);
|
|
795
|
-
console.log(result._id);
|
|
796
|
-
}
|
|
797
|
-
);
|
|
798
|
-
context.command("update <contextId>").description("Update a context").option("-n, --name <name>", "New name").option("-t, --text <text>", "New text content").option("-f, --file <path>", "Read text content from file").option("--json", "Output as JSON").action(
|
|
799
|
-
async (contextId, opts) => {
|
|
800
|
-
const client = getClient();
|
|
801
|
-
let text = opts.text;
|
|
802
|
-
if (opts.file) {
|
|
803
|
-
text = readFileSync4(opts.file, "utf-8");
|
|
804
|
-
}
|
|
805
|
-
await client.patch(`/api/cli/contexts/${contextId}`, {
|
|
806
|
-
name: opts.name,
|
|
807
|
-
text
|
|
808
|
-
});
|
|
809
|
-
if (opts.json) {
|
|
810
|
-
printJson({ _id: contextId });
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
console.error(`Updated context: ${contextId}`);
|
|
814
|
-
console.log(contextId);
|
|
815
|
-
}
|
|
816
|
-
);
|
|
817
|
-
context.command("delete <contextId>").description("Delete a context").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
818
|
-
const client = getClient();
|
|
819
|
-
await client.delete(`/api/cli/contexts/${contextId}`);
|
|
820
|
-
if (opts.json) {
|
|
821
|
-
printJson({ _id: contextId });
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
console.error(`Deleted context: ${contextId}`);
|
|
825
|
-
console.log(contextId);
|
|
826
|
-
});
|
|
827
|
-
context.command("link <contextId>").description("Link a context to a project").requiredOption("--project <projectId>", "Project ID to link to").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
828
|
-
const client = getClient();
|
|
829
|
-
await client.post(`/api/cli/contexts/${contextId}/link`, {
|
|
830
|
-
taskId: opts.project
|
|
831
|
-
});
|
|
832
|
-
if (opts.json) {
|
|
833
|
-
printJson({ _id: contextId, project: opts.project });
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
console.error(`Linked context ${contextId} to project ${opts.project}`);
|
|
837
|
-
console.log(contextId);
|
|
838
|
-
});
|
|
839
|
-
context.command("unlink <contextId>").description("Unlink a context from a project").requiredOption("--project <projectId>", "Project ID to unlink from").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
840
|
-
const client = getClient();
|
|
841
|
-
await client.post(`/api/cli/contexts/${contextId}/unlink`, {
|
|
842
|
-
taskId: opts.project
|
|
843
|
-
});
|
|
844
|
-
if (opts.json) {
|
|
845
|
-
printJson({ _id: contextId, project: opts.project });
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
console.error(`Unlinked context ${contextId} from project ${opts.project}`);
|
|
849
|
-
console.log(contextId);
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
function printContextList(contexts) {
|
|
853
|
-
if (contexts.length === 0) {
|
|
854
|
-
console.error("No contexts found.");
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
for (const ctx of contexts) {
|
|
858
|
-
const scope = ctx.scope === "task" ? "project" : ctx.scope ?? "project";
|
|
859
|
-
const spec = ctx.isSpecDocument ? " [SPEC]" : "";
|
|
860
|
-
const name = ctx.name ?? ctx.title ?? "(unnamed)";
|
|
861
|
-
console.log(`${ctx._id} ${scope.padEnd(8)} ${name}${spec}`);
|
|
862
|
-
}
|
|
863
|
-
console.error(`
|
|
864
|
-
${contexts.length} context(s)`);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
749
|
// src/commands/daemon.ts
|
|
868
750
|
import { spawn } from "child_process";
|
|
869
|
-
import { existsSync as existsSync4, readFileSync as
|
|
751
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
870
752
|
import { homedir as homedir3 } from "os";
|
|
871
753
|
import { join as join4 } from "path";
|
|
872
754
|
var DAEMON_BIN = "prim-daemon-server";
|
|
@@ -885,7 +767,7 @@ function readPidfile() {
|
|
|
885
767
|
if (!existsSync4(PID_PATH)) {
|
|
886
768
|
return null;
|
|
887
769
|
}
|
|
888
|
-
const raw =
|
|
770
|
+
const raw = readFileSync4(PID_PATH, "utf-8").trim();
|
|
889
771
|
const pid = Number(raw);
|
|
890
772
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
891
773
|
return null;
|
|
@@ -1668,7 +1550,7 @@ function registerDecisionsCommands(program2) {
|
|
|
1668
1550
|
|
|
1669
1551
|
// src/commands/hooks.ts
|
|
1670
1552
|
import { execSync } from "child_process";
|
|
1671
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as
|
|
1553
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1672
1554
|
import { resolve } from "path";
|
|
1673
1555
|
import { Option } from "commander";
|
|
1674
1556
|
var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
|
|
@@ -1717,7 +1599,7 @@ function detectHusky(gitRoot) {
|
|
|
1717
1599
|
const pkgPath = resolve(gitRoot, "package.json");
|
|
1718
1600
|
if (existsSync5(pkgPath)) {
|
|
1719
1601
|
try {
|
|
1720
|
-
const pkg2 = JSON.parse(
|
|
1602
|
+
const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
1721
1603
|
const scripts = pkg2.scripts ?? {};
|
|
1722
1604
|
if (/husky/i.test(scripts.prepare ?? "") || /husky/i.test(scripts.postinstall ?? "")) {
|
|
1723
1605
|
return true;
|
|
@@ -1745,7 +1627,7 @@ async function askConfirmation(question) {
|
|
|
1745
1627
|
function installToHusky(gitRoot, spec = PRE_COMMIT) {
|
|
1746
1628
|
const hookPath = resolve(gitRoot, ".husky", spec.hookName);
|
|
1747
1629
|
if (existsSync5(hookPath)) {
|
|
1748
|
-
const existing =
|
|
1630
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
1749
1631
|
if (containsPrimHook(existing, spec.binName)) {
|
|
1750
1632
|
console.log(`Prim ${spec.hookName} hook is already installed in .husky/${spec.hookName}.`);
|
|
1751
1633
|
return;
|
|
@@ -1773,7 +1655,7 @@ function installToDotGit(gitRoot, spec = PRE_COMMIT) {
|
|
|
1773
1655
|
mkdirSync3(hooksDir, { recursive: true });
|
|
1774
1656
|
}
|
|
1775
1657
|
if (existsSync5(hookPath)) {
|
|
1776
|
-
const existing =
|
|
1658
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
1777
1659
|
if (containsPrimHook(existing, spec.binName)) {
|
|
1778
1660
|
console.log(`Prim ${spec.hookName} hook is already installed at ${hookPath}.`);
|
|
1779
1661
|
return;
|
|
@@ -1840,7 +1722,7 @@ function registerHooksCommands(program2) {
|
|
|
1840
1722
|
console.log(`No ${spec.hookName} hook found.`);
|
|
1841
1723
|
continue;
|
|
1842
1724
|
}
|
|
1843
|
-
if (containsPrimHook(
|
|
1725
|
+
if (containsPrimHook(readFileSync5(hookPath, "utf-8"), spec.binName)) {
|
|
1844
1726
|
unlinkSync2(hookPath);
|
|
1845
1727
|
console.log(`Removed ${spec.hookName} hook at ${hookPath}`);
|
|
1846
1728
|
} else {
|
|
@@ -1982,28 +1864,6 @@ function registerMovesCommands(program2) {
|
|
|
1982
1864
|
});
|
|
1983
1865
|
}
|
|
1984
1866
|
|
|
1985
|
-
// src/commands/project.ts
|
|
1986
|
-
function registerProjectCommands(program2) {
|
|
1987
|
-
const project = program2.command("project").description("Manage projects");
|
|
1988
|
-
project.command("create").description("Create a new project").requiredOption("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("--spec <contextId>", "Link an existing spec as this project's spec").option("--json", "Output as JSON").action(async (opts) => {
|
|
1989
|
-
const client = getClient();
|
|
1990
|
-
const result = await client.post("/api/cli/tasks", {
|
|
1991
|
-
name: opts.name,
|
|
1992
|
-
description: opts.description,
|
|
1993
|
-
specContextId: opts.spec
|
|
1994
|
-
});
|
|
1995
|
-
if (opts.json) {
|
|
1996
|
-
printJson(opts.spec ? { _id: result._id, spec: opts.spec } : { _id: result._id });
|
|
1997
|
-
return;
|
|
1998
|
-
}
|
|
1999
|
-
console.error(`Created project: ${result._id}`);
|
|
2000
|
-
if (opts.spec) {
|
|
2001
|
-
console.error(`Linked spec: ${opts.spec}`);
|
|
2002
|
-
}
|
|
2003
|
-
console.log(result._id);
|
|
2004
|
-
});
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
1867
|
// src/commands/reconcile.ts
|
|
2008
1868
|
var EXIT_OK2 = 0;
|
|
2009
1869
|
var EXIT_USAGE = 2;
|
|
@@ -2090,7 +1950,7 @@ function registerReconcileCommands(program2) {
|
|
|
2090
1950
|
import {
|
|
2091
1951
|
existsSync as existsSync7,
|
|
2092
1952
|
mkdirSync as mkdirSync5,
|
|
2093
|
-
readFileSync as
|
|
1953
|
+
readFileSync as readFileSync6,
|
|
2094
1954
|
readdirSync,
|
|
2095
1955
|
unlinkSync as unlinkSync5,
|
|
2096
1956
|
writeFileSync as writeFileSync5
|
|
@@ -2132,7 +1992,7 @@ function registerSessionCommands(program2) {
|
|
|
2132
1992
|
for (const f of files) {
|
|
2133
1993
|
const sessionId = f.replace(/\.json$/, "");
|
|
2134
1994
|
try {
|
|
2135
|
-
const m = JSON.parse(
|
|
1995
|
+
const m = JSON.parse(readFileSync6(join6(SESSIONS_DIR, f), "utf-8"));
|
|
2136
1996
|
console.log(`${sessionId} org=${m.orgId}`);
|
|
2137
1997
|
} catch {
|
|
2138
1998
|
}
|
|
@@ -2155,7 +2015,7 @@ import {
|
|
|
2155
2015
|
existsSync as existsSync8,
|
|
2156
2016
|
fsyncSync as fsyncSync2,
|
|
2157
2017
|
openSync as openSync2,
|
|
2158
|
-
readFileSync as
|
|
2018
|
+
readFileSync as readFileSync7,
|
|
2159
2019
|
renameSync as renameSync3,
|
|
2160
2020
|
writeFileSync as writeFileSync6
|
|
2161
2021
|
} from "fs";
|
|
@@ -2177,7 +2037,7 @@ function loadSkill() {
|
|
|
2177
2037
|
let dir = __dirname;
|
|
2178
2038
|
while (dir !== dirname4(dir)) {
|
|
2179
2039
|
const p = resolve2(dir, "SKILL.md");
|
|
2180
|
-
if (existsSync8(p)) return
|
|
2040
|
+
if (existsSync8(p)) return readFileSync7(p, "utf-8");
|
|
2181
2041
|
dir = dirname4(dir);
|
|
2182
2042
|
}
|
|
2183
2043
|
throw new Error("SKILL.md not found in package");
|
|
@@ -2232,7 +2092,7 @@ function resolveTarget(cwd, override) {
|
|
|
2232
2092
|
function runInstall(cwd, opts) {
|
|
2233
2093
|
const target = resolveTarget(cwd, opts.target);
|
|
2234
2094
|
if (target === null) return 1;
|
|
2235
|
-
const existing = existsSync8(target) ?
|
|
2095
|
+
const existing = existsSync8(target) ? readFileSync7(target, "utf-8") : "";
|
|
2236
2096
|
const eol = existing ? detectNewline(existing) : "\n";
|
|
2237
2097
|
const block = composeBlock(loadSkill(), eol);
|
|
2238
2098
|
const next = applyBlock(existing, block, eol);
|
|
@@ -2255,7 +2115,7 @@ function runUninstall(cwd, opts) {
|
|
|
2255
2115
|
console.log(`Skill block not present at ${target}`);
|
|
2256
2116
|
return 0;
|
|
2257
2117
|
}
|
|
2258
|
-
const existing =
|
|
2118
|
+
const existing = readFileSync7(target, "utf-8");
|
|
2259
2119
|
const next = removeBlock(existing);
|
|
2260
2120
|
if (next === null) {
|
|
2261
2121
|
console.log(`Skill block not present at ${target}`);
|
|
@@ -2271,7 +2131,7 @@ function runStatus(cwd, opts) {
|
|
|
2271
2131
|
const fileExists = existsSync8(target);
|
|
2272
2132
|
let installed = false;
|
|
2273
2133
|
if (fileExists) {
|
|
2274
|
-
const content =
|
|
2134
|
+
const content = readFileSync7(target, "utf-8");
|
|
2275
2135
|
installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
|
|
2276
2136
|
}
|
|
2277
2137
|
if (opts.json) {
|
|
@@ -2307,274 +2167,8 @@ function registerSkillCommands(program2) {
|
|
|
2307
2167
|
});
|
|
2308
2168
|
}
|
|
2309
2169
|
|
|
2310
|
-
// src/commands/spec.ts
|
|
2311
|
-
import { readFileSync as readFileSync9 } from "fs";
|
|
2312
|
-
function registerSpecCommands(program2) {
|
|
2313
|
-
const spec = program2.command("spec").description("Manage spec documents");
|
|
2314
|
-
spec.command("list").description("List spec documents").option("-t, --project-id <projectId>", "List spec for a specific root project").option("--json", "Output as JSON").action(async (opts) => {
|
|
2315
|
-
const client = getClient();
|
|
2316
|
-
if (opts.projectId) {
|
|
2317
|
-
const specs = await client.get(`/api/cli/specs?rootTaskId=${opts.projectId}`);
|
|
2318
|
-
if (opts.json) {
|
|
2319
|
-
printJson(specs[0] ?? null);
|
|
2320
|
-
return;
|
|
2321
|
-
}
|
|
2322
|
-
if (specs.length === 0) {
|
|
2323
|
-
console.error("No spec document found for this project.");
|
|
2324
|
-
return;
|
|
2325
|
-
}
|
|
2326
|
-
printSpec(specs[0]);
|
|
2327
|
-
return;
|
|
2328
|
-
}
|
|
2329
|
-
const contexts = await client.get("/api/cli/specs");
|
|
2330
|
-
if (opts.json) {
|
|
2331
|
-
printJson(contexts);
|
|
2332
|
-
return;
|
|
2333
|
-
}
|
|
2334
|
-
if (contexts.length === 0) {
|
|
2335
|
-
console.error("No spec documents found.");
|
|
2336
|
-
return;
|
|
2337
|
-
}
|
|
2338
|
-
for (const ctx of contexts) {
|
|
2339
|
-
const scope = ctx.scope === "task" ? "project" : ctx.scope ?? "project";
|
|
2340
|
-
const review = ctx.specReviewStatus ?? "\u2014";
|
|
2341
|
-
const name = ctx.name ?? "(unnamed)";
|
|
2342
|
-
console.log(`${ctx._id} ${scope.padEnd(8)} ${String(review).padEnd(10)} ${name}`);
|
|
2343
|
-
}
|
|
2344
|
-
console.error(`
|
|
2345
|
-
${contexts.length} spec(s)`);
|
|
2346
|
-
});
|
|
2347
|
-
spec.command("get <contextId>").description("Get a spec document by ID").option("--text-only", "Print only the text content (no metadata)").option("--json", "Output as JSON (overrides --text-only)").action(async (contextId, opts) => {
|
|
2348
|
-
const client = getClient();
|
|
2349
|
-
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
2350
|
-
if (opts.json) {
|
|
2351
|
-
printJson(ctx);
|
|
2352
|
-
return;
|
|
2353
|
-
}
|
|
2354
|
-
if (opts.textOnly) {
|
|
2355
|
-
console.log(ctx.text ?? "");
|
|
2356
|
-
return;
|
|
2357
|
-
}
|
|
2358
|
-
printSpec(ctx);
|
|
2359
|
-
});
|
|
2360
|
-
spec.command("create").description("Create a new spec document").requiredOption("-s, --scope <scope>", "Scope: project, global, external").requiredOption("-n, --name <name>", "Spec name").option("-t, --text <text>", "Spec text content").option("-f, --file <path>", "Read text content from file").option("--project-id <projectId>", "Link to project(s), comma-separated").option("--branch <branch>", "Link spec to this branch on the current repo").option("--pr <prNumber>", "Optional PR number to attach to the link").option("--json", "Output as JSON").action(
|
|
2361
|
-
async (opts) => {
|
|
2362
|
-
const client = getClient();
|
|
2363
|
-
let text = opts.text;
|
|
2364
|
-
if (opts.file) {
|
|
2365
|
-
text = readFileSync9(opts.file, "utf-8");
|
|
2366
|
-
}
|
|
2367
|
-
const taskIds = opts.projectId ? opts.projectId.split(",").map((id) => id.trim()) : void 0;
|
|
2368
|
-
let linkedBranch;
|
|
2369
|
-
if (opts.branch) {
|
|
2370
|
-
const { repoFullName } = getGitContext();
|
|
2371
|
-
if (!repoFullName) {
|
|
2372
|
-
console.warn(
|
|
2373
|
-
"[prim] --branch supplied but origin remote is not GitHub; skipping link."
|
|
2374
|
-
);
|
|
2375
|
-
} else {
|
|
2376
|
-
linkedBranch = { repoFullName, branch: opts.branch };
|
|
2377
|
-
if (opts.pr) {
|
|
2378
|
-
const n = Number.parseInt(opts.pr, 10);
|
|
2379
|
-
if (Number.isFinite(n)) linkedBranch.prNumber = n;
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
const result = await client.post("/api/cli/contexts", {
|
|
2384
|
-
scope: opts.scope === "project" ? "task" : opts.scope,
|
|
2385
|
-
name: opts.name,
|
|
2386
|
-
text,
|
|
2387
|
-
taskIds,
|
|
2388
|
-
isSpecDocument: true,
|
|
2389
|
-
linkedBranch
|
|
2390
|
-
});
|
|
2391
|
-
if (opts.json) {
|
|
2392
|
-
printJson({ _id: result._id });
|
|
2393
|
-
return;
|
|
2394
|
-
}
|
|
2395
|
-
console.error(
|
|
2396
|
-
`Created spec: ${result._id}${linkedBranch ? ` (linked to ${linkedBranch.branch})` : ""}`
|
|
2397
|
-
);
|
|
2398
|
-
console.log(result._id);
|
|
2399
|
-
}
|
|
2400
|
-
);
|
|
2401
|
-
spec.command("update <contextId>").description("Update a spec document's text content").option("-t, --text <text>", "New text content").option("-f, --file <path>", "Read text content from file").option("-n, --name <name>", "New name").option("--json", "Output as JSON").action(
|
|
2402
|
-
async (contextId, opts) => {
|
|
2403
|
-
const client = getClient();
|
|
2404
|
-
let text = opts.text;
|
|
2405
|
-
if (opts.file) {
|
|
2406
|
-
text = readFileSync9(opts.file, "utf-8");
|
|
2407
|
-
}
|
|
2408
|
-
if (!(text || opts.name)) {
|
|
2409
|
-
console.error("Provide --text, --file, or --name to update.");
|
|
2410
|
-
process.exit(1);
|
|
2411
|
-
}
|
|
2412
|
-
await client.patch(`/api/cli/contexts/${contextId}`, {
|
|
2413
|
-
name: opts.name,
|
|
2414
|
-
text,
|
|
2415
|
-
skipTiptapLifecycle: !!text
|
|
2416
|
-
});
|
|
2417
|
-
if (text) {
|
|
2418
|
-
await client.post(`/api/cli/contexts/${contextId}/inject`);
|
|
2419
|
-
}
|
|
2420
|
-
if (opts.json) {
|
|
2421
|
-
printJson({ _id: contextId });
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
console.error(`Updated spec: ${contextId}`);
|
|
2425
|
-
console.log(contextId);
|
|
2426
|
-
}
|
|
2427
|
-
);
|
|
2428
|
-
spec.command("sync <contextId>").description("Trigger spec \u2194 project DAG synchronization").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2429
|
-
const client = getClient();
|
|
2430
|
-
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
2431
|
-
if (!ctx.isSpecDocument) {
|
|
2432
|
-
console.error("Context is not a spec document. Use `prim context` instead.");
|
|
2433
|
-
process.exit(1);
|
|
2434
|
-
}
|
|
2435
|
-
await client.post(`/api/cli/contexts/${contextId}/sync`);
|
|
2436
|
-
if (opts.json) {
|
|
2437
|
-
printJson(
|
|
2438
|
-
ctx.specRootTaskId ? { _id: contextId, specRootTaskId: ctx.specRootTaskId } : { _id: contextId }
|
|
2439
|
-
);
|
|
2440
|
-
return;
|
|
2441
|
-
}
|
|
2442
|
-
console.error(`Triggered sync for spec: ${contextId}`);
|
|
2443
|
-
if (ctx.specRootTaskId) {
|
|
2444
|
-
console.error(`Root project: ${ctx.specRootTaskId}`);
|
|
2445
|
-
}
|
|
2446
|
-
console.log(contextId);
|
|
2447
|
-
});
|
|
2448
|
-
spec.command("review <contextId>").description("Manually trigger the PR Intent Review bot for a spec").requiredOption("--pr <prNumber>", "PR number to review against").option("--sha <headSha>", "Commit SHA the review runs against (defaults to current HEAD)").action(async (contextId, opts) => {
|
|
2449
|
-
const prNumber = Number.parseInt(opts.pr, 10);
|
|
2450
|
-
if (!Number.isFinite(prNumber)) {
|
|
2451
|
-
console.error("--pr must be an integer.");
|
|
2452
|
-
process.exit(1);
|
|
2453
|
-
}
|
|
2454
|
-
const headSha = opts.sha ?? getGitContext().sha;
|
|
2455
|
-
if (!headSha) {
|
|
2456
|
-
console.error("Could not determine head SHA \u2014 pass --sha or run inside a git checkout.");
|
|
2457
|
-
process.exit(1);
|
|
2458
|
-
}
|
|
2459
|
-
const client = getClient();
|
|
2460
|
-
await client.post(`/api/cli/contexts/${contextId}/review`, {
|
|
2461
|
-
prNumber,
|
|
2462
|
-
headSha
|
|
2463
|
-
});
|
|
2464
|
-
console.log(
|
|
2465
|
-
`Scheduled review: ${contextId} against PR #${String(prNumber)} @ ${headSha.slice(0, 7)}`
|
|
2466
|
-
);
|
|
2467
|
-
});
|
|
2468
|
-
spec.command("drift <contextId>").description("Dispatch the Claude Code drift-fix workflow against a PR").requiredOption("--pr <prNumber>", "PR number to dispatch the drift-fix workflow against").action(async (contextId, opts) => {
|
|
2469
|
-
const prNumber = Number.parseInt(opts.pr, 10);
|
|
2470
|
-
if (!Number.isFinite(prNumber)) {
|
|
2471
|
-
console.error("--pr must be an integer.");
|
|
2472
|
-
process.exit(1);
|
|
2473
|
-
}
|
|
2474
|
-
const client = getClient();
|
|
2475
|
-
const result = await client.post(`/api/cli/contexts/${contextId}/drift`, {
|
|
2476
|
-
prNumber
|
|
2477
|
-
});
|
|
2478
|
-
if (result.dispatched) {
|
|
2479
|
-
const ref = result.runUrl ? `: ${result.runUrl}` : "";
|
|
2480
|
-
console.log(`Dispatched drift-fix workflow${ref}`);
|
|
2481
|
-
} else {
|
|
2482
|
-
console.error(
|
|
2483
|
-
"Drift-fix dispatch failed. Likely causes: actions:write App scope not granted, primitive-drift-fix.yml workflow file missing, or no findings on the latest review."
|
|
2484
|
-
);
|
|
2485
|
-
process.exit(1);
|
|
2486
|
-
}
|
|
2487
|
-
});
|
|
2488
|
-
spec.command("status <taskId>").description(
|
|
2489
|
-
"Show task status, auto-complete suppression flag, and the most-recent bot auto-completion"
|
|
2490
|
-
).action(async (taskId) => {
|
|
2491
|
-
const client = getClient();
|
|
2492
|
-
const result = await client.get(`/api/cli/tasks/${taskId}/status`);
|
|
2493
|
-
console.log(`status: ${result.status}`);
|
|
2494
|
-
console.log(`auto-complete suppressed: ${result.autoCompleteSuppressed ? "yes" : "no"}`);
|
|
2495
|
-
const last = result.lastAutoCompleteActivity;
|
|
2496
|
-
if (last) {
|
|
2497
|
-
const when = last.createdAt ? new Date(last.createdAt).toISOString() : "\u2014";
|
|
2498
|
-
const pr = last.prNumber ? `#${String(last.prNumber)}` : "\u2014";
|
|
2499
|
-
console.log(`last auto-complete: ${when} (PR ${pr})`);
|
|
2500
|
-
if (last.explanation) {
|
|
2501
|
-
console.log(` ${last.explanation}`);
|
|
2502
|
-
}
|
|
2503
|
-
} else {
|
|
2504
|
-
console.log("last auto-complete: \u2014");
|
|
2505
|
-
}
|
|
2506
|
-
});
|
|
2507
|
-
spec.command("map <contextId>").description("Map file patterns to a spec (used by pre-commit hook to detect affected specs)").requiredOption(
|
|
2508
|
-
"-p, --pattern <patterns...>",
|
|
2509
|
-
'Glob pattern(s) to associate, e.g. "src/auth/**"'
|
|
2510
|
-
).option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2511
|
-
const client = getClient();
|
|
2512
|
-
const result = await client.post(`/api/cli/contexts/${contextId}/map`, {
|
|
2513
|
-
patterns: opts.pattern
|
|
2514
|
-
});
|
|
2515
|
-
if (opts.json) {
|
|
2516
|
-
printJson({ _id: contextId, filePatterns: result.filePatterns });
|
|
2517
|
-
return;
|
|
2518
|
-
}
|
|
2519
|
-
console.error(`Mapped patterns to spec ${contextId}:`);
|
|
2520
|
-
for (const p of result.filePatterns) {
|
|
2521
|
-
console.error(` ${p}`);
|
|
2522
|
-
}
|
|
2523
|
-
console.log(contextId);
|
|
2524
|
-
});
|
|
2525
|
-
spec.command("unmap <contextId>").description("Remove file pattern mappings from a spec (omit --pattern to clear all)").option("-p, --pattern <patterns...>", "Specific pattern(s) to remove (omit to clear all)").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2526
|
-
const client = getClient();
|
|
2527
|
-
const result = await client.post(`/api/cli/contexts/${contextId}/unmap`, {
|
|
2528
|
-
patterns: opts.pattern
|
|
2529
|
-
});
|
|
2530
|
-
if (opts.json) {
|
|
2531
|
-
printJson({ _id: contextId, filePatterns: result.filePatterns });
|
|
2532
|
-
return;
|
|
2533
|
-
}
|
|
2534
|
-
if (result.filePatterns.length === 0) {
|
|
2535
|
-
console.error(`Cleared all file patterns from spec ${contextId}`);
|
|
2536
|
-
} else {
|
|
2537
|
-
console.error(`Updated patterns for spec ${contextId}:`);
|
|
2538
|
-
for (const p of result.filePatterns) {
|
|
2539
|
-
console.error(` ${p}`);
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
console.log(contextId);
|
|
2543
|
-
});
|
|
2544
|
-
spec.command("auto-map <contextId>").description("Trigger auto-mapping of file patterns for a spec").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2545
|
-
const client = getClient();
|
|
2546
|
-
await client.post(`/api/cli/contexts/${contextId}/auto-map`);
|
|
2547
|
-
if (opts.json) {
|
|
2548
|
-
printJson({ _id: contextId });
|
|
2549
|
-
return;
|
|
2550
|
-
}
|
|
2551
|
-
console.error(`Auto-mapping triggered for spec: ${contextId}`);
|
|
2552
|
-
console.log(contextId);
|
|
2553
|
-
});
|
|
2554
|
-
}
|
|
2555
|
-
function printSpec(ctx) {
|
|
2556
|
-
const name = ctx.name ?? ctx.title ?? "(unnamed)";
|
|
2557
|
-
const review = ctx.specReviewStatus ?? "\u2014";
|
|
2558
|
-
const patterns = ctx.filePatterns;
|
|
2559
|
-
console.log(`ID: ${ctx._id}`);
|
|
2560
|
-
console.log(`Name: ${name}`);
|
|
2561
|
-
console.log(`Scope: ${ctx.scope === "task" ? "project" : ctx.scope ?? "project"}`);
|
|
2562
|
-
console.log(`Review Status: ${review}`);
|
|
2563
|
-
console.log(`Root Project: ${ctx.specRootTaskId ?? "\u2014"}`);
|
|
2564
|
-
console.log(`Sync Version: ${ctx.syncVersion ?? 0}`);
|
|
2565
|
-
console.log(`Index Status: ${ctx.indexStatus ?? "\u2014"}`);
|
|
2566
|
-
console.log(`File Patterns: ${patterns?.length ? patterns.join(", ") : "\u2014"}`);
|
|
2567
|
-
if (ctx.text) {
|
|
2568
|
-
const text = ctx.text;
|
|
2569
|
-
const preview = text.length > 500 ? `${text.slice(0, 500)}\u2026` : text;
|
|
2570
|
-
console.log(`
|
|
2571
|
-
--- Text ---
|
|
2572
|
-
${preview}`);
|
|
2573
|
-
}
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
2170
|
// src/commands/statusline.ts
|
|
2577
|
-
import { readFileSync as
|
|
2171
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
2578
2172
|
import { dirname as dirname5, resolve as resolve3 } from "path";
|
|
2579
2173
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2580
2174
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
@@ -2584,7 +2178,7 @@ function readPackageVersion() {
|
|
|
2584
2178
|
const candidates = [resolve3(here, "../../package.json"), resolve3(here, "../package.json")];
|
|
2585
2179
|
for (const path of candidates) {
|
|
2586
2180
|
try {
|
|
2587
|
-
const pkg2 = JSON.parse(
|
|
2181
|
+
const pkg2 = JSON.parse(readFileSync8(path, "utf-8"));
|
|
2588
2182
|
if (pkg2.version) {
|
|
2589
2183
|
return pkg2.version;
|
|
2590
2184
|
}
|
|
@@ -2630,17 +2224,14 @@ function registerStatuslineCommands(program2) {
|
|
|
2630
2224
|
|
|
2631
2225
|
// src/index.ts
|
|
2632
2226
|
var __dirname2 = dirname6(fileURLToPath4(import.meta.url));
|
|
2633
|
-
var pkg = JSON.parse(
|
|
2227
|
+
var pkg = JSON.parse(readFileSync9(resolve4(__dirname2, "../package.json"), "utf-8"));
|
|
2634
2228
|
updateNotifier({ pkg }).notify();
|
|
2635
2229
|
var program = new Command();
|
|
2636
|
-
program.name("prim").description("CLI for
|
|
2230
|
+
program.name("prim").description("CLI for Primitive's decision graph").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(
|
|
2637
2231
|
"--non-interactive",
|
|
2638
2232
|
"fail fast instead of prompting (also: CI=1, PRIM_NON_INTERACTIVE=1)"
|
|
2639
2233
|
);
|
|
2640
2234
|
registerAuthCommands(program);
|
|
2641
|
-
registerContextCommands(program);
|
|
2642
|
-
registerSpecCommands(program);
|
|
2643
|
-
registerProjectCommands(program);
|
|
2644
2235
|
registerHooksCommands(program);
|
|
2645
2236
|
registerSkillCommands(program);
|
|
2646
2237
|
registerMovesCommands(program);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitive.ai/prim",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"description": "CLI for
|
|
3
|
+
"version": "0.1.0-alpha.20",
|
|
4
|
+
"description": "CLI for Primitive's decision graph — passive decision capture, conflict gate, and team presence",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"primitive",
|
|
20
20
|
"prim",
|
|
21
21
|
"cli",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
22
|
+
"decisions",
|
|
23
|
+
"decision-graph",
|
|
24
24
|
"pre-commit"
|
|
25
25
|
],
|
|
26
26
|
"engines": {
|