@shakecodeslikecray/npc-cli 0.4.3 → 0.4.5
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/client/api.d.ts +15 -1
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +47 -2
- package/dist/client/api.js.map +1 -1
- package/dist/commands/fetch.d.ts +31 -0
- package/dist/commands/fetch.d.ts.map +1 -0
- package/dist/commands/fetch.js +77 -0
- package/dist/commands/fetch.js.map +1 -0
- package/dist/commands/hook.d.ts +3 -0
- package/dist/commands/hook.d.ts.map +1 -0
- package/dist/commands/hook.js +284 -0
- package/dist/commands/hook.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +145 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +247 -10
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync, chmodSync, mkdirSync, } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import * as api from "../client/api.js";
|
|
6
|
+
import { CREDENTIALS_PATH } from "../config.js";
|
|
7
|
+
import { fetchAndCache } from "./fetch.js";
|
|
8
|
+
function ask(question) {
|
|
9
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
rl.question(question, (answer) => {
|
|
12
|
+
rl.close();
|
|
13
|
+
resolve(answer.trim());
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function init(opts) {
|
|
18
|
+
// 1. Check auth
|
|
19
|
+
if (!existsSync(CREDENTIALS_PATH)) {
|
|
20
|
+
console.error("Not authenticated. Run: npc auth login");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
// 2. Check .git
|
|
24
|
+
if (!existsSync(".git")) {
|
|
25
|
+
throw new Error("Not a git repository. Run this from a git repo root.");
|
|
26
|
+
}
|
|
27
|
+
// 3. Pick project
|
|
28
|
+
let projectId = opts.project;
|
|
29
|
+
let projectName = "";
|
|
30
|
+
if (!projectId) {
|
|
31
|
+
const projects = await api.listProjects();
|
|
32
|
+
if (projects.length === 0) {
|
|
33
|
+
throw new Error("No projects found. Create one first in the NPC dashboard.");
|
|
34
|
+
}
|
|
35
|
+
console.log("\nProjects:");
|
|
36
|
+
for (let i = 0; i < projects.length; i++) {
|
|
37
|
+
console.log(` ${i + 1}) ${projects[i].name}`);
|
|
38
|
+
}
|
|
39
|
+
const choice = await ask(`\nSelect project [1-${projects.length}]: `);
|
|
40
|
+
const idx = parseInt(choice, 10) - 1;
|
|
41
|
+
if (isNaN(idx) || idx < 0 || idx >= projects.length) {
|
|
42
|
+
throw new Error("Invalid selection.");
|
|
43
|
+
}
|
|
44
|
+
projectId = projects[idx].id;
|
|
45
|
+
projectName = projects[idx].name;
|
|
46
|
+
}
|
|
47
|
+
// 4. Pick repo
|
|
48
|
+
let repoId = opts.repo;
|
|
49
|
+
let repoName = "";
|
|
50
|
+
if (!repoId) {
|
|
51
|
+
const repos = await api.getProjectRepos(projectId);
|
|
52
|
+
if (repos.length === 0) {
|
|
53
|
+
throw new Error("No repos found for this project. Create one first.");
|
|
54
|
+
}
|
|
55
|
+
console.log("\nRepos:");
|
|
56
|
+
for (let i = 0; i < repos.length; i++) {
|
|
57
|
+
console.log(` ${i + 1}) ${repos[i].name}`);
|
|
58
|
+
}
|
|
59
|
+
const choice = await ask(`\nSelect repo [1-${repos.length}]: `);
|
|
60
|
+
const idx = parseInt(choice, 10) - 1;
|
|
61
|
+
if (isNaN(idx) || idx < 0 || idx >= repos.length) {
|
|
62
|
+
throw new Error("Invalid selection.");
|
|
63
|
+
}
|
|
64
|
+
repoId = repos[idx].id;
|
|
65
|
+
repoName = repos[idx].name;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// If repo ID was provided directly, fetch repos to get the name
|
|
69
|
+
const repos = await api.getProjectRepos(projectId);
|
|
70
|
+
const match = repos.find((r) => r.id === repoId);
|
|
71
|
+
repoName = match?.name ?? repoId;
|
|
72
|
+
}
|
|
73
|
+
// 5. Write .npcrc.json
|
|
74
|
+
const npcrc = { project_id: projectId, repo_id: repoId, repo_name: repoName };
|
|
75
|
+
writeFileSync(".npcrc.json", JSON.stringify(npcrc, null, 2) + "\n");
|
|
76
|
+
console.log("\n[ok] .npcrc.json written");
|
|
77
|
+
// 6. Add .npc/ to .gitignore
|
|
78
|
+
const gitignorePath = ".gitignore";
|
|
79
|
+
let gitignore = existsSync(gitignorePath)
|
|
80
|
+
? readFileSync(gitignorePath, "utf-8")
|
|
81
|
+
: "";
|
|
82
|
+
if (!gitignore.includes(".npc/")) {
|
|
83
|
+
gitignore = gitignore.trimEnd() + "\n.npc/\n";
|
|
84
|
+
writeFileSync(gitignorePath, gitignore);
|
|
85
|
+
console.log("[ok] .npc/ added to .gitignore");
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.log("[ok] .npc/ already in .gitignore");
|
|
89
|
+
}
|
|
90
|
+
// 7. Install git hooks
|
|
91
|
+
if (opts.hooks !== false) {
|
|
92
|
+
const hooksDir = join(".git", "hooks");
|
|
93
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
94
|
+
const hooks = {
|
|
95
|
+
"pre-push": "#!/bin/sh\nexec npc hook pre-push \"$@\"\n",
|
|
96
|
+
"pre-commit": "#!/bin/sh\nexec npc hook pre-commit \"$@\"\n",
|
|
97
|
+
};
|
|
98
|
+
for (const [name, content] of Object.entries(hooks)) {
|
|
99
|
+
const hookPath = join(hooksDir, name);
|
|
100
|
+
// Skip if already installed
|
|
101
|
+
if (existsSync(hookPath)) {
|
|
102
|
+
const existing = readFileSync(hookPath, "utf-8");
|
|
103
|
+
if (existing.includes("npc hook")) {
|
|
104
|
+
console.log(`[ok] ${name} hook already installed`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// Back up existing hook
|
|
108
|
+
const bakPath = `${hookPath}.bak`;
|
|
109
|
+
copyFileSync(hookPath, bakPath);
|
|
110
|
+
console.log(`[ok] backed up existing ${name} hook to ${name}.bak`);
|
|
111
|
+
}
|
|
112
|
+
writeFileSync(hookPath, content);
|
|
113
|
+
chmodSync(hookPath, 0o755);
|
|
114
|
+
console.log(`[ok] ${name} hook installed`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.log("[--] hooks skipped (--no-hooks)");
|
|
119
|
+
}
|
|
120
|
+
// 8. Run npc fetch
|
|
121
|
+
if (opts.fetch !== false) {
|
|
122
|
+
console.log("\nFetching work items...");
|
|
123
|
+
try {
|
|
124
|
+
const result = await fetchAndCache(repoId);
|
|
125
|
+
console.log(`[ok] Cached ${result.item_count} items, ${result.edge_count} edges, ${result.cr_count} CRs`);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.warn(`[warn] fetch failed: ${err.message || err}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.log("[--] fetch skipped (--no-fetch)");
|
|
133
|
+
}
|
|
134
|
+
// 9. Detect Claude Code
|
|
135
|
+
try {
|
|
136
|
+
execFileSync("which", ["claude"], { stdio: "pipe" });
|
|
137
|
+
console.log("\nClaude Code detected. Install NPC plugins:\n" +
|
|
138
|
+
" claude plugins:add-marketplace shakecodeslikecray/fordel-plugins");
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// claude not found, skip
|
|
142
|
+
}
|
|
143
|
+
console.log("\nDone. Repository initialized for NPC.");
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,QAA0B,CAAC;AAC/C,eAAO,MAAM,gBAAgB,QAAoC,CAAC;AAClE,eAAO,MAAM,eAAe,sCAAsC,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAkC,CAAC"}
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,QAA0B,CAAC;AAC/C,eAAO,MAAM,aAAa,SAAS,CAAC;AACpC,eAAO,MAAM,aAAa,QAAyB,CAAC;AACpD,eAAO,MAAM,gBAAgB,QAAoC,CAAC;AAClE,eAAO,MAAM,eAAe,sCAAsC,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAkC,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
export const NPC_DIR = join(homedir(), ".npc");
|
|
4
|
+
export const NPC_LOCAL_DIR = ".npc";
|
|
5
|
+
export const NPC_CACHE_DIR = join(NPC_DIR, "cache");
|
|
4
6
|
export const CREDENTIALS_PATH = join(NPC_DIR, "credentials.json");
|
|
5
7
|
export const DEFAULT_API_URL = "https://npc-api.fordelstudios.com";
|
|
6
8
|
/**
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;AAC/C,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG,mCAAmC,CAAC;AAEnE;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;AAC/C,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC;AACpC,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG,mCAAmC,CAAC;AAEnE;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,9 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import * as auth from "./client/auth.js";
|
|
8
8
|
import * as api from "./client/api.js";
|
|
9
9
|
import { CREDENTIALS_PATH, DEFAULT_API_URL } from "./config.js";
|
|
10
|
+
import { readNpcrc, fetchAndCache } from "./commands/fetch.js";
|
|
11
|
+
import { prePush, preCommit } from "./commands/hook.js";
|
|
12
|
+
import { init } from "./commands/init.js";
|
|
10
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
14
|
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8"));
|
|
12
15
|
function prompt(question) {
|
|
@@ -437,20 +440,71 @@ program
|
|
|
437
440
|
});
|
|
438
441
|
program
|
|
439
442
|
.command("pull")
|
|
440
|
-
.description("
|
|
443
|
+
.description("Pull next L2 feature from the queue with all tasks")
|
|
441
444
|
.option("-r, --repo <id>", "Filter by repo UUID")
|
|
445
|
+
.option("--id <uuid>", "Pull a specific L2 by ID")
|
|
446
|
+
.option("--l4", "Legacy: pull a single L4 task instead of L2 feature")
|
|
442
447
|
.action(async (opts) => {
|
|
443
448
|
try {
|
|
444
|
-
const
|
|
449
|
+
const level = opts.l4 ? "l4" : "l2";
|
|
450
|
+
const result = await api.queueNext(opts.repo, level, opts.id);
|
|
445
451
|
const wi = result.work_item;
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
console.log(`
|
|
452
|
-
|
|
453
|
-
|
|
452
|
+
if (level === "l4") {
|
|
453
|
+
// Legacy single-task output
|
|
454
|
+
console.log(`Task: ${wi.title}`);
|
|
455
|
+
console.log(`ID: ${wi.id}`);
|
|
456
|
+
console.log(`State: ${wi.state}`);
|
|
457
|
+
console.log(`Priority: ${wi.priority}`);
|
|
458
|
+
if (wi.description)
|
|
459
|
+
console.log(`Description: ${wi.description}`);
|
|
460
|
+
if (result.ancestors.length > 0) {
|
|
461
|
+
console.log(`Context: ${result.ancestors.map((a) => a.title).join(" > ")}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
// L2 feature tree output
|
|
466
|
+
console.log(`\nFeature: ${wi.title} (${wi.state})`);
|
|
467
|
+
console.log(`ID: ${wi.id}`);
|
|
468
|
+
if (wi.priority && wi.priority !== "none")
|
|
469
|
+
console.log(`Priority: ${wi.priority}`);
|
|
470
|
+
if (result.ancestors.length > 0) {
|
|
471
|
+
console.log(`Context: ${result.ancestors.map((a) => a.title).join(" > ")}`);
|
|
472
|
+
}
|
|
473
|
+
if (wi.description)
|
|
474
|
+
console.log(`\n${wi.description}`);
|
|
475
|
+
// Group descendants into L3 → L4 tree
|
|
476
|
+
const descendants = result.descendants || [];
|
|
477
|
+
const l3s = descendants.filter((d) => d.parent_id === wi.id);
|
|
478
|
+
const l4sByParent = new Map();
|
|
479
|
+
for (const d of descendants) {
|
|
480
|
+
if (d.parent_id && d.parent_id !== wi.id) {
|
|
481
|
+
const arr = l4sByParent.get(d.parent_id) || [];
|
|
482
|
+
arr.push(d);
|
|
483
|
+
l4sByParent.set(d.parent_id, arr);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (l3s.length > 0) {
|
|
487
|
+
console.log(`\nTasks:`);
|
|
488
|
+
for (const l3 of l3s) {
|
|
489
|
+
console.log(` ${l3.title} [L3]`);
|
|
490
|
+
const children = l4sByParent.get(l3.id) || [];
|
|
491
|
+
for (const l4 of children) {
|
|
492
|
+
const blocked = l4.state === "todo" && l4.claimed_by ? "blocked" : "unblocked";
|
|
493
|
+
const stateTag = l4.claimed_by && l4.state === "doing" ? "doing" : l4.state;
|
|
494
|
+
console.log(` [${l4.id.slice(0, 8)}] ${l4.title.padEnd(40)} ${stateTag} (${blocked})`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// Summary
|
|
499
|
+
const leaves = descendants.filter((d) => !descendants.some((c) => c.parent_id === d.id));
|
|
500
|
+
const doing = leaves.filter((l) => l.state === "doing").length;
|
|
501
|
+
const todo = leaves.filter((l) => l.state === "todo").length;
|
|
502
|
+
const done = leaves.filter((l) => l.state === "done").length;
|
|
503
|
+
console.log(`\n${leaves.length} tasks: ${doing} doing, ${todo} todo, ${done} done`);
|
|
504
|
+
// Auto-refresh local cache after pulling
|
|
505
|
+
const rc = readNpcrc();
|
|
506
|
+
if (rc)
|
|
507
|
+
fetchAndCache(rc.repo_id).catch(() => { });
|
|
454
508
|
}
|
|
455
509
|
}
|
|
456
510
|
catch (err) {
|
|
@@ -458,6 +512,19 @@ program
|
|
|
458
512
|
process.exit(1);
|
|
459
513
|
}
|
|
460
514
|
});
|
|
515
|
+
program
|
|
516
|
+
.command("release <task-id>")
|
|
517
|
+
.description("Release a claimed L2 feature back to the queue")
|
|
518
|
+
.action(async (taskId) => {
|
|
519
|
+
try {
|
|
520
|
+
await api.releaseWorkItem(taskId);
|
|
521
|
+
console.log(`Released: ${taskId}`);
|
|
522
|
+
}
|
|
523
|
+
catch (err) {
|
|
524
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
461
528
|
program
|
|
462
529
|
.command("start <task-id>")
|
|
463
530
|
.description("Start working on a task")
|
|
@@ -1063,5 +1130,175 @@ testCaseCmd
|
|
|
1063
1130
|
process.exit(1);
|
|
1064
1131
|
}
|
|
1065
1132
|
});
|
|
1133
|
+
// ─── Change Requests ───
|
|
1134
|
+
const crCmd = program.command("cr").description("Change request operations");
|
|
1135
|
+
crCmd
|
|
1136
|
+
.command("list")
|
|
1137
|
+
.description("List change requests")
|
|
1138
|
+
.option("-r, --repo <id>", "Repo ID (reads .npcrc.json if omitted)")
|
|
1139
|
+
.option("-s, --state <state>", "Filter by state")
|
|
1140
|
+
.action(async (opts) => {
|
|
1141
|
+
try {
|
|
1142
|
+
const repoId = opts.repo || (await readNpcrc())?.repo_id;
|
|
1143
|
+
if (!repoId) {
|
|
1144
|
+
console.error("No repo ID. Use --repo or add .npcrc.json");
|
|
1145
|
+
process.exit(1);
|
|
1146
|
+
}
|
|
1147
|
+
const crs = await api.listChangeRequests(repoId, opts.state);
|
|
1148
|
+
if (crs.length === 0) {
|
|
1149
|
+
console.log("No change requests found.");
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
console.table(crs.map((cr) => ({
|
|
1153
|
+
id: cr.id.slice(0, 8),
|
|
1154
|
+
type: cr.cr_type,
|
|
1155
|
+
state: cr.state,
|
|
1156
|
+
title: cr.title.slice(0, 50),
|
|
1157
|
+
hours: cr.estimated_hours ?? "--",
|
|
1158
|
+
submitted: cr.submitter_name || "Unknown",
|
|
1159
|
+
})));
|
|
1160
|
+
}
|
|
1161
|
+
catch (err) {
|
|
1162
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1163
|
+
process.exit(1);
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
crCmd
|
|
1167
|
+
.command("pending-qa")
|
|
1168
|
+
.description("List scheduled CRs that need QA items generated")
|
|
1169
|
+
.option("-r, --repo <id>", "Repo ID (reads .npcrc.json if omitted)")
|
|
1170
|
+
.action(async (opts) => {
|
|
1171
|
+
try {
|
|
1172
|
+
const repoId = opts.repo || (await readNpcrc())?.repo_id;
|
|
1173
|
+
if (!repoId) {
|
|
1174
|
+
console.error("No repo ID. Use --repo or add .npcrc.json");
|
|
1175
|
+
process.exit(1);
|
|
1176
|
+
}
|
|
1177
|
+
// Get scheduled CRs
|
|
1178
|
+
const crs = await api.listChangeRequests(repoId, "scheduled");
|
|
1179
|
+
if (crs.length === 0) {
|
|
1180
|
+
console.log("No scheduled CRs.");
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
// Get user flows and their use cases to check existing QA coverage
|
|
1184
|
+
const flows = await api.listUserFlows(repoId);
|
|
1185
|
+
const existingUCTitles = new Set();
|
|
1186
|
+
for (const flow of flows) {
|
|
1187
|
+
const ucs = await api.listUseCases(repoId, flow.id);
|
|
1188
|
+
for (const uc of ucs)
|
|
1189
|
+
existingUCTitles.add(uc.title);
|
|
1190
|
+
}
|
|
1191
|
+
// Filter CRs that don't have matching use cases
|
|
1192
|
+
const pending = crs.filter((cr) => !existingUCTitles.has(`CR: ${cr.title}`));
|
|
1193
|
+
if (pending.length === 0) {
|
|
1194
|
+
console.log("All scheduled CRs have QA coverage.");
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
console.log(`\n${pending.length} CR(s) need QA items:\n`);
|
|
1198
|
+
for (const cr of pending) {
|
|
1199
|
+
console.log(` [${cr.id.slice(0, 8)}] ${cr.title}`);
|
|
1200
|
+
console.log(` Type: ${cr.cr_type} | Hours: ${cr.estimated_hours ?? "--"} | By: ${cr.submitter_name || "Unknown"}`);
|
|
1201
|
+
if (cr.description)
|
|
1202
|
+
console.log(` ${cr.description.slice(0, 120)}`);
|
|
1203
|
+
console.log();
|
|
1204
|
+
}
|
|
1205
|
+
// Output machine-readable JSON for the cron agent
|
|
1206
|
+
console.log("---JSON---");
|
|
1207
|
+
console.log(JSON.stringify(pending.map((cr) => ({
|
|
1208
|
+
id: cr.id,
|
|
1209
|
+
title: cr.title,
|
|
1210
|
+
description: cr.description,
|
|
1211
|
+
cr_type: cr.cr_type,
|
|
1212
|
+
linked_l1: cr.linked_l1,
|
|
1213
|
+
}))));
|
|
1214
|
+
}
|
|
1215
|
+
catch (err) {
|
|
1216
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
// ─── Fetch ───
|
|
1221
|
+
program
|
|
1222
|
+
.command("fetch")
|
|
1223
|
+
.description("Fetch and cache work items, edges, and CRs for the current repo")
|
|
1224
|
+
.option("-r, --repo <id>", "Repo ID (reads .npcrc.json if omitted)")
|
|
1225
|
+
.option("--force", "Ignore cache staleness, always fetch")
|
|
1226
|
+
.option("-s, --state <state>", "Filter items by state (todo, doing, done)")
|
|
1227
|
+
.option("-q, --search <query>", "Search items by title")
|
|
1228
|
+
.option("--sort <field>", "Sort by field (created_at, updated_at, sort_order)")
|
|
1229
|
+
.option("--labels <labels>", "Filter by labels (comma-separated)")
|
|
1230
|
+
.option("--per-page <n>", "Items per page (default: all)")
|
|
1231
|
+
.action(async (opts) => {
|
|
1232
|
+
try {
|
|
1233
|
+
const repoId = opts.repo || readNpcrc()?.repo_id;
|
|
1234
|
+
if (!repoId) {
|
|
1235
|
+
console.error("No repo ID. Use --repo or add .npcrc.json");
|
|
1236
|
+
process.exit(1);
|
|
1237
|
+
}
|
|
1238
|
+
const params = {};
|
|
1239
|
+
if (opts.state)
|
|
1240
|
+
params.state = opts.state;
|
|
1241
|
+
if (opts.search)
|
|
1242
|
+
params.q = opts.search;
|
|
1243
|
+
if (opts.sort)
|
|
1244
|
+
params.sort = opts.sort;
|
|
1245
|
+
if (opts.labels)
|
|
1246
|
+
params.labels = opts.labels;
|
|
1247
|
+
if (opts.perPage)
|
|
1248
|
+
params.per_page = opts.perPage;
|
|
1249
|
+
console.log("Fetching data from NPC API...");
|
|
1250
|
+
const result = await fetchAndCache(repoId, params);
|
|
1251
|
+
console.log(`Cached ${result.item_count} items, ${result.edge_count} edges, ${result.cr_count} CRs`);
|
|
1252
|
+
console.log(`Cache dir: ${result.cache_dir}`);
|
|
1253
|
+
}
|
|
1254
|
+
catch (err) {
|
|
1255
|
+
console.error("Error:", err instanceof Error ? err.message : err);
|
|
1256
|
+
process.exit(1);
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
// ---------------------------------------------------------------------------
|
|
1260
|
+
// Hook commands (called by git hooks)
|
|
1261
|
+
// ---------------------------------------------------------------------------
|
|
1262
|
+
const hookCmd = program.command("hook").description("Git hook handlers (called by git hooks)");
|
|
1263
|
+
hookCmd
|
|
1264
|
+
.command("pre-push")
|
|
1265
|
+
.description("Pre-push hook: signals, CR detection, telemetry")
|
|
1266
|
+
.action(async () => {
|
|
1267
|
+
try {
|
|
1268
|
+
await prePush();
|
|
1269
|
+
}
|
|
1270
|
+
catch (e) {
|
|
1271
|
+
console.log("NPC: Hook error (push continues):", e.message || e);
|
|
1272
|
+
}
|
|
1273
|
+
process.exit(0);
|
|
1274
|
+
});
|
|
1275
|
+
hookCmd
|
|
1276
|
+
.command("pre-commit")
|
|
1277
|
+
.description("Pre-commit hook: task tracking enforcement")
|
|
1278
|
+
.action(async () => {
|
|
1279
|
+
try {
|
|
1280
|
+
await preCommit();
|
|
1281
|
+
}
|
|
1282
|
+
catch (e) {
|
|
1283
|
+
console.error("NPC:", e.message || e);
|
|
1284
|
+
process.exit(1);
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
program
|
|
1288
|
+
.command("init")
|
|
1289
|
+
.description("Initialize NPC in current repo: auth, config, hooks, cache")
|
|
1290
|
+
.option("-p, --project <id>", "Project UUID (skip interactive)")
|
|
1291
|
+
.option("-r, --repo <id>", "Repo UUID (skip interactive)")
|
|
1292
|
+
.option("--no-hooks", "Skip git hook installation")
|
|
1293
|
+
.option("--no-fetch", "Skip initial data fetch")
|
|
1294
|
+
.action(async (opts) => {
|
|
1295
|
+
try {
|
|
1296
|
+
await init(opts);
|
|
1297
|
+
}
|
|
1298
|
+
catch (err) {
|
|
1299
|
+
console.error("Error:", err.message || err);
|
|
1300
|
+
process.exit(1);
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1066
1303
|
program.parse();
|
|
1067
1304
|
//# sourceMappingURL=index.js.map
|