schub 0.1.2 → 0.1.3
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/index.js +263 -52
- package/package.json +1 -1
- package/skills/create-proposal/SKILL.md +1 -1
- package/skills/create-tasks/SKILL.md +3 -3
- package/skills/review-proposal/SKILL.md +2 -2
- package/src/App.test.tsx +54 -2
- package/src/App.tsx +19 -2
- package/src/changes.test.ts +52 -0
- package/src/changes.ts +30 -7
- package/src/commands/adr.test.ts +1 -1
- package/src/commands/changes.test.ts +134 -12
- package/src/commands/changes.ts +102 -1
- package/src/commands/cookbook.test.ts +1 -1
- package/src/commands/init.test.ts +69 -2
- package/src/commands/init.ts +43 -5
- package/src/commands/review.test.ts +2 -2
- package/src/commands/review.ts +1 -1
- package/src/commands/tasks-create.test.ts +21 -21
- package/src/commands/tasks-list.test.ts +27 -27
- package/src/components/PlanView.test.tsx +14 -14
- package/src/components/StatusView.test.tsx +88 -36
- package/src/components/StatusView.tsx +56 -3
- package/src/features/tasks/create.ts +5 -5
- package/src/features/tasks/filesystem.test.ts +68 -18
- package/src/features/tasks/filesystem.ts +32 -3
- package/src/features/tasks/index.ts +1 -1
- package/src/index.ts +11 -1
- package/src/opencode.ts +6 -0
- package/src/tasks.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -35183,7 +35183,7 @@ var import_react28 = __toESM(require_react(), 1);
|
|
|
35183
35183
|
// package.json
|
|
35184
35184
|
var package_default = {
|
|
35185
35185
|
name: "schub",
|
|
35186
|
-
version: "0.1.
|
|
35186
|
+
version: "0.1.3",
|
|
35187
35187
|
type: "module",
|
|
35188
35188
|
bin: {
|
|
35189
35189
|
schub: "./src/index.ts"
|
|
@@ -35265,11 +35265,11 @@ var createTask = (schubDir, options) => {
|
|
|
35265
35265
|
const changeId = options.changeId.trim();
|
|
35266
35266
|
const status = (options.status || "backlog").trim();
|
|
35267
35267
|
const titles = options.titles.map((t) => t.trim()).filter(Boolean);
|
|
35268
|
-
if (!/^(?:[Cc]\d{
|
|
35269
|
-
throw new Error(`Invalid change-id '${changeId}'. Use kebab-case or a C-prefixed id (e.g.,
|
|
35268
|
+
if (!/^(?:[Cc]\d{4}_)?[a-z0-9]+(?:-[a-z0-9]+)*$/.test(changeId)) {
|
|
35269
|
+
throw new Error(`Invalid change-id '${changeId}'. Use kebab-case or a C-prefixed id (e.g., C0001_add-user-auth).`);
|
|
35270
35270
|
}
|
|
35271
35271
|
let normalizedChangeId = changeId;
|
|
35272
|
-
const match = changeId.match(/^([Cc])(\d{
|
|
35272
|
+
const match = changeId.match(/^([Cc])(\d{4})_(.+)$/);
|
|
35273
35273
|
if (match) {
|
|
35274
35274
|
normalizedChangeId = `C${match[2]}_${match[3]}`;
|
|
35275
35275
|
}
|
|
@@ -35308,7 +35308,7 @@ Mark the proposal as Accepted before scaffolding tasks.`);
|
|
|
35308
35308
|
if (entry.isDirectory()) {
|
|
35309
35309
|
scan(join2(dir, entry.name));
|
|
35310
35310
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
35311
|
-
const m = entry.name.match(/(?:^|-)T(\d{
|
|
35311
|
+
const m = entry.name.match(/(?:^|-)T(\d{4})(?:_[^.]+)?\.md$/);
|
|
35312
35312
|
if (m)
|
|
35313
35313
|
existingNumbers.add(Number.parseInt(m[1], 10));
|
|
35314
35314
|
}
|
|
@@ -35322,7 +35322,7 @@ Mark the proposal as Accepted before scaffolding tasks.`);
|
|
|
35322
35322
|
const template = readTaskTemplate(schubDir);
|
|
35323
35323
|
const createdPaths = [];
|
|
35324
35324
|
for (const title of titles) {
|
|
35325
|
-
const taskId = `T${nextNumber.toString().padStart(
|
|
35325
|
+
const taskId = `T${nextNumber.toString().padStart(4, "0")}`;
|
|
35326
35326
|
let slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
35327
35327
|
if (!slug)
|
|
35328
35328
|
slug = "task";
|
|
@@ -35339,8 +35339,8 @@ Mark the proposal as Accepted before scaffolding tasks.`);
|
|
|
35339
35339
|
return createdPaths;
|
|
35340
35340
|
};
|
|
35341
35341
|
// src/features/tasks/filesystem.ts
|
|
35342
|
-
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
35343
|
-
import { dirname, join as join3, relative, resolve } from "node:path";
|
|
35342
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, renameSync, statSync as statSync2 } from "node:fs";
|
|
35343
|
+
import { basename, dirname, join as join3, relative, resolve } from "node:path";
|
|
35344
35344
|
|
|
35345
35345
|
// src/features/tasks/sorting.ts
|
|
35346
35346
|
var taskNumber = (id) => {
|
|
@@ -35388,6 +35388,9 @@ var parseTaskFilename = (fileName) => {
|
|
|
35388
35388
|
return null;
|
|
35389
35389
|
}
|
|
35390
35390
|
const id = baseName.slice(0, underscoreIndex);
|
|
35391
|
+
if (!/^T\d{4}$/.test(id)) {
|
|
35392
|
+
return null;
|
|
35393
|
+
}
|
|
35391
35394
|
const titleSlug = baseName.slice(underscoreIndex + 1);
|
|
35392
35395
|
return { id, title: titleSlug.replace(/-/g, " ") };
|
|
35393
35396
|
};
|
|
@@ -35441,7 +35444,7 @@ var parseTaskFile = (filePath, fallback) => {
|
|
|
35441
35444
|
if (dependsMatch) {
|
|
35442
35445
|
const raw = dependsMatch[1].trim();
|
|
35443
35446
|
if (!/^none$/i.test(raw)) {
|
|
35444
|
-
const matches = raw.match(/\bT\d
|
|
35447
|
+
const matches = raw.match(/\bT\d{4}\b/g);
|
|
35445
35448
|
if (matches) {
|
|
35446
35449
|
dependsOn = Array.from(new Set(matches));
|
|
35447
35450
|
}
|
|
@@ -35537,6 +35540,25 @@ var loadTaskDependencies = (schubDir, statuses = TASK_STATUSES) => {
|
|
|
35537
35540
|
}
|
|
35538
35541
|
return tasks.sort(compareTasks);
|
|
35539
35542
|
};
|
|
35543
|
+
var ACTIVE_TASK_STATUSES = TASK_STATUSES.filter((status) => status !== "archived");
|
|
35544
|
+
var archiveTasksForChange = (schubDir, changeId) => {
|
|
35545
|
+
const normalizedChangeId = changeId.trim();
|
|
35546
|
+
const tasksRoot = join3(schubDir, "tasks");
|
|
35547
|
+
const archiveRoot = join3(tasksRoot, "archived");
|
|
35548
|
+
mkdirSync2(archiveRoot, { recursive: true });
|
|
35549
|
+
const repoRoot = dirname(schubDir);
|
|
35550
|
+
const tasks = loadTaskDependencies(schubDir, ACTIVE_TASK_STATUSES).filter((task) => task.changeId === normalizedChangeId);
|
|
35551
|
+
return tasks.map((task) => {
|
|
35552
|
+
const currentPath = join3(repoRoot, task.path);
|
|
35553
|
+
const archivePath = join3(archiveRoot, basename(task.path));
|
|
35554
|
+
renameSync(currentPath, archivePath);
|
|
35555
|
+
return {
|
|
35556
|
+
...task,
|
|
35557
|
+
status: "archived",
|
|
35558
|
+
path: relative(repoRoot, archivePath)
|
|
35559
|
+
};
|
|
35560
|
+
});
|
|
35561
|
+
};
|
|
35540
35562
|
// src/features/tasks/graph.ts
|
|
35541
35563
|
var buildTaskGraph = (tasks) => {
|
|
35542
35564
|
const tasksById = new Map(tasks.map((task) => [task.id, task]));
|
|
@@ -35774,13 +35796,13 @@ import { dirname as dirname5, normalize, sep } from "node:path";
|
|
|
35774
35796
|
var import_react27 = __toESM(require_react(), 1);
|
|
35775
35797
|
|
|
35776
35798
|
// src/changes.ts
|
|
35777
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
35799
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, statSync as statSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
35778
35800
|
import { dirname as dirname4, join as join5, relative as relative2 } from "node:path";
|
|
35779
35801
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
35780
35802
|
|
|
35781
35803
|
// src/schub-root.ts
|
|
35782
35804
|
import { statSync as statSync3 } from "node:fs";
|
|
35783
|
-
import { basename, dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
|
|
35805
|
+
import { basename as basename2, dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
|
|
35784
35806
|
var isDirectory2 = (path) => {
|
|
35785
35807
|
try {
|
|
35786
35808
|
return statSync3(path).isDirectory();
|
|
@@ -35793,7 +35815,7 @@ var resolveSchubRoot = (startDir = process.cwd()) => {
|
|
|
35793
35815
|
const fallback = join4(start, ".schub");
|
|
35794
35816
|
let current = start;
|
|
35795
35817
|
while (true) {
|
|
35796
|
-
if (
|
|
35818
|
+
if (basename2(current) === ".schub" && isDirectory2(current)) {
|
|
35797
35819
|
return current;
|
|
35798
35820
|
}
|
|
35799
35821
|
const candidate = join4(current, ".schub");
|
|
@@ -35809,7 +35831,7 @@ var resolveSchubRoot = (startDir = process.cwd()) => {
|
|
|
35809
35831
|
};
|
|
35810
35832
|
|
|
35811
35833
|
// src/changes.ts
|
|
35812
|
-
var CHANGE_ID_PATTERN = /^(?:[Cc]\d{
|
|
35834
|
+
var CHANGE_ID_PATTERN = /^(?:[Cc]\d{4}_)?[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
35813
35835
|
var isDirectory3 = (path) => {
|
|
35814
35836
|
try {
|
|
35815
35837
|
return statSync4(path).isDirectory();
|
|
@@ -35827,7 +35849,7 @@ var parseProposal = (content, changeId) => {
|
|
|
35827
35849
|
};
|
|
35828
35850
|
var normalizeChangeId = (value) => {
|
|
35829
35851
|
const trimmed = value.trim();
|
|
35830
|
-
const match = trimmed.match(/^([Cc])(\d{
|
|
35852
|
+
const match = trimmed.match(/^([Cc])(\d{4})_(.+)$/);
|
|
35831
35853
|
if (match) {
|
|
35832
35854
|
return `C${match[2]}_${match[3]}`;
|
|
35833
35855
|
}
|
|
@@ -35840,7 +35862,7 @@ var readChangeSummary = (schubDir, changeId) => {
|
|
|
35840
35862
|
throw new Error("Provide --change-id.");
|
|
35841
35863
|
}
|
|
35842
35864
|
if (!isValidChangeId(trimmed)) {
|
|
35843
|
-
throw new Error(`Invalid change-id '${changeId}'. Use kebab-case or a C-prefixed id (e.g.,
|
|
35865
|
+
throw new Error(`Invalid change-id '${changeId}'. Use kebab-case or a C-prefixed id (e.g., C0001_add-user-auth).`);
|
|
35844
35866
|
}
|
|
35845
35867
|
const normalized = normalizeChangeId(trimmed);
|
|
35846
35868
|
const changeDir = join5(schubDir, "changes", normalized);
|
|
@@ -35881,6 +35903,24 @@ Add a '**Status**: <value>' line before updating status.`);
|
|
|
35881
35903
|
status: nextStatus
|
|
35882
35904
|
};
|
|
35883
35905
|
};
|
|
35906
|
+
var archiveChange = (schubDir, changeId) => {
|
|
35907
|
+
const summary = readChangeSummary(schubDir, changeId);
|
|
35908
|
+
const archiveRoot = join5(schubDir, "archive", "changes");
|
|
35909
|
+
const archivePath = join5(archiveRoot, summary.changeId);
|
|
35910
|
+
if (existsSync4(archivePath)) {
|
|
35911
|
+
throw new Error(`Archive already exists: ${archivePath}`);
|
|
35912
|
+
}
|
|
35913
|
+
mkdirSync3(archiveRoot, { recursive: true });
|
|
35914
|
+
const updated = updateChangeStatus(schubDir, summary.changeId, "Archived");
|
|
35915
|
+
renameSync2(summary.changeDir, archivePath);
|
|
35916
|
+
return {
|
|
35917
|
+
changeId: updated.changeId,
|
|
35918
|
+
previousStatus: updated.previousStatus,
|
|
35919
|
+
status: updated.status,
|
|
35920
|
+
proposalPath: join5(archivePath, "proposal.md"),
|
|
35921
|
+
archivePath
|
|
35922
|
+
};
|
|
35923
|
+
};
|
|
35884
35924
|
var changeNumber = (id) => {
|
|
35885
35925
|
const match = id.match(/\d+/);
|
|
35886
35926
|
return match ? Number(match[0]) : Number.POSITIVE_INFINITY;
|
|
@@ -35925,7 +35965,7 @@ var slugify = (value) => {
|
|
|
35925
35965
|
return slug.replace(/^-|-$/g, "");
|
|
35926
35966
|
};
|
|
35927
35967
|
var splitPrefixedChangeId = (changeId) => {
|
|
35928
|
-
const match = changeId.match(/^([Cc])(\d{
|
|
35968
|
+
const match = changeId.match(/^([Cc])(\d{4})_([a-z0-9]+(?:-[a-z0-9]+)*)$/);
|
|
35929
35969
|
if (match) {
|
|
35930
35970
|
return { prefix: match[2], slug: match[3] };
|
|
35931
35971
|
}
|
|
@@ -35942,7 +35982,7 @@ var nextChangePrefix = (schubDir) => {
|
|
|
35942
35982
|
if (!entry.isDirectory()) {
|
|
35943
35983
|
continue;
|
|
35944
35984
|
}
|
|
35945
|
-
const match = entry.name.match(/^[Cc](\d{
|
|
35985
|
+
const match = entry.name.match(/^[Cc](\d{4})_/);
|
|
35946
35986
|
if (match) {
|
|
35947
35987
|
prefixes.push(Number.parseInt(match[1], 10));
|
|
35948
35988
|
}
|
|
@@ -35951,7 +35991,7 @@ var nextChangePrefix = (schubDir) => {
|
|
|
35951
35991
|
scan(changesRoot);
|
|
35952
35992
|
scan(archiveRoot);
|
|
35953
35993
|
const next = prefixes.length > 0 ? Math.max(...prefixes) + 1 : 1;
|
|
35954
|
-
return next.toString().padStart(
|
|
35994
|
+
return next.toString().padStart(4, "0");
|
|
35955
35995
|
};
|
|
35956
35996
|
var CHANGE_PREFIX = "C";
|
|
35957
35997
|
var BUNDLED_PROPOSAL_TEMPLATE_PATH = fileURLToPath2(new URL("../templates/create-proposal/proposal-template.md", import.meta.url));
|
|
@@ -36024,17 +36064,25 @@ var createChange = (schubDir, options) => {
|
|
|
36024
36064
|
if (existsSync4(proposalPath) && !options.overwrite) {
|
|
36025
36065
|
throw new Error(`Refusing to overwrite existing file: ${proposalPath}`);
|
|
36026
36066
|
}
|
|
36027
|
-
|
|
36067
|
+
mkdirSync3(changeDir, { recursive: true });
|
|
36028
36068
|
writeFileSync2(proposalPath, rendered, "utf8");
|
|
36029
36069
|
return proposalPath;
|
|
36030
36070
|
};
|
|
36031
36071
|
|
|
36072
|
+
// src/opencode.ts
|
|
36073
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
36074
|
+
var launchOpencodeReview = (changeId) => {
|
|
36075
|
+
const prompt = `review ${changeId}`;
|
|
36076
|
+
spawn2("opencode", [prompt], { stdio: "ignore", detached: true }).unref();
|
|
36077
|
+
};
|
|
36078
|
+
|
|
36032
36079
|
// src/components/StatusView.tsx
|
|
36033
36080
|
var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
|
|
36034
36081
|
var DEFAULT_REFRESH_INTERVAL_MS2 = 1000;
|
|
36035
|
-
var
|
|
36036
|
-
var CHANGE_TASK_STATUSES = [...
|
|
36082
|
+
var ACTIVE_TASK_STATUSES2 = ["blocked", "wip", "ready", "backlog"];
|
|
36083
|
+
var CHANGE_TASK_STATUSES = [...ACTIVE_TASK_STATUSES2, "done"];
|
|
36037
36084
|
var AUTO_MARK_STATUSES = new Set(["accepted", "wip"]);
|
|
36085
|
+
var REVIEW_SHORTCUT = { keyLabel: "r", label: "review" };
|
|
36038
36086
|
var compareText = (left, right) => left.localeCompare(right, undefined, { sensitivity: "base" });
|
|
36039
36087
|
var sortByStatusThenTitle = (left, right) => {
|
|
36040
36088
|
const statusCompare = compareText(left.status, right.status);
|
|
@@ -36052,9 +36100,20 @@ var isTaskItem = (item) => {
|
|
|
36052
36100
|
return parts.includes("tasks");
|
|
36053
36101
|
};
|
|
36054
36102
|
var formatChangeId = (value) => {
|
|
36055
|
-
const match = value.match(/^([Cc]\d{
|
|
36103
|
+
const match = value.match(/^([Cc]\d{4})_/);
|
|
36056
36104
|
return match ? match[1].toUpperCase() : value;
|
|
36057
36105
|
};
|
|
36106
|
+
var formatChangeTitle = (changeId, title) => {
|
|
36107
|
+
const trimmedTitle = title.trim();
|
|
36108
|
+
if (trimmedTitle !== changeId) {
|
|
36109
|
+
return trimmedTitle;
|
|
36110
|
+
}
|
|
36111
|
+
const match = changeId.match(/^([Cc]\d{4})_(.+)$/);
|
|
36112
|
+
if (!match) {
|
|
36113
|
+
return trimmedTitle;
|
|
36114
|
+
}
|
|
36115
|
+
return match[2].split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
36116
|
+
};
|
|
36058
36117
|
var canAutoMarkChange = (status) => AUTO_MARK_STATUSES.has(status.trim().toLowerCase());
|
|
36059
36118
|
var autoMarkChangesDone = (schubDir) => {
|
|
36060
36119
|
const allTasks = loadTaskDependencies(schubDir, CHANGE_TASK_STATUSES);
|
|
@@ -36167,7 +36226,12 @@ var buildStatusData = (schubDir) => {
|
|
|
36167
36226
|
pendingImplementationCounts
|
|
36168
36227
|
};
|
|
36169
36228
|
};
|
|
36170
|
-
function StatusView({
|
|
36229
|
+
function StatusView({
|
|
36230
|
+
refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS2,
|
|
36231
|
+
onCopyId,
|
|
36232
|
+
onReview = launchOpencodeReview,
|
|
36233
|
+
onShortcutsChange
|
|
36234
|
+
}) {
|
|
36171
36235
|
const schubDir = findSchubRoot();
|
|
36172
36236
|
const [, setRefreshTick] = import_react27.default.useState(0);
|
|
36173
36237
|
const {
|
|
@@ -36194,6 +36258,20 @@ function StatusView({ refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS2, onCopyId
|
|
|
36194
36258
|
];
|
|
36195
36259
|
const [selection, setSelection] = import_react27.default.useState(0);
|
|
36196
36260
|
const totalItems = allItems.length;
|
|
36261
|
+
const pendingReviewIds = new Set(pendingReview.map((change) => change.id));
|
|
36262
|
+
const selectedItem = totalItems > 0 ? allItems[selection] : null;
|
|
36263
|
+
const selectedReviewId = selectedItem && !isTaskItem(selectedItem) && pendingReviewIds.has(selectedItem.id) ? selectedItem.id : null;
|
|
36264
|
+
const lastReviewIdRef = import_react27.default.useRef(null);
|
|
36265
|
+
import_react27.default.useEffect(() => {
|
|
36266
|
+
if (!onShortcutsChange) {
|
|
36267
|
+
return;
|
|
36268
|
+
}
|
|
36269
|
+
if (lastReviewIdRef.current === selectedReviewId) {
|
|
36270
|
+
return;
|
|
36271
|
+
}
|
|
36272
|
+
lastReviewIdRef.current = selectedReviewId;
|
|
36273
|
+
onShortcutsChange(selectedReviewId ? [REVIEW_SHORTCUT] : []);
|
|
36274
|
+
}, [onShortcutsChange, selectedReviewId]);
|
|
36197
36275
|
import_react27.default.useEffect(() => {
|
|
36198
36276
|
if (!schubDir) {
|
|
36199
36277
|
return;
|
|
@@ -36224,12 +36302,15 @@ function StatusView({ refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS2, onCopyId
|
|
|
36224
36302
|
setSelection((current) => Math.max(current - 1, 0));
|
|
36225
36303
|
}
|
|
36226
36304
|
if (input === "o") {
|
|
36227
|
-
const
|
|
36228
|
-
openInVsCode(repoRoot,
|
|
36305
|
+
const selectedItem2 = allItems[selection];
|
|
36306
|
+
openInVsCode(repoRoot, selectedItem2.path);
|
|
36229
36307
|
}
|
|
36230
36308
|
if (input === "c") {
|
|
36231
|
-
const
|
|
36232
|
-
onCopyId(
|
|
36309
|
+
const selectedItem2 = allItems[selection];
|
|
36310
|
+
onCopyId(selectedItem2.id);
|
|
36311
|
+
}
|
|
36312
|
+
if (input === "r" && selectedReviewId) {
|
|
36313
|
+
onReview(selectedReviewId);
|
|
36233
36314
|
}
|
|
36234
36315
|
});
|
|
36235
36316
|
if (!schubDir) {
|
|
@@ -36261,7 +36342,7 @@ function StatusView({ refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS2, onCopyId
|
|
|
36261
36342
|
const task = isTaskItem(item) ? item : null;
|
|
36262
36343
|
const title = task ? trimTaskTitle(task.title) : "";
|
|
36263
36344
|
const checklistIndicator = task && task.status === "wip" && typeof task.checklistTotal === "number" ? ` (${task.checklistRemaining ?? 0}/${task.checklistTotal})` : "";
|
|
36264
|
-
const changeTitle = item.title;
|
|
36345
|
+
const changeTitle = task ? "" : formatChangeTitle(item.id, item.title);
|
|
36265
36346
|
const changeDetail = detail ? ` ${detail}` : "";
|
|
36266
36347
|
const displayTitle = task ? `${title}${checklistIndicator}` : `${changeTitle}${changeDetail}`.trim();
|
|
36267
36348
|
const displayId = task ? item.id : formatChangeId(item.id);
|
|
@@ -36386,6 +36467,7 @@ function App2({ copyToClipboard: copyToClipboard2 = copyToClipboard }) {
|
|
|
36386
36467
|
rows: stdout.rows
|
|
36387
36468
|
}));
|
|
36388
36469
|
const [copyBanner, setCopyBanner] = import_react28.default.useState(null);
|
|
36470
|
+
const [statusShortcuts, setStatusShortcuts] = import_react28.default.useState([]);
|
|
36389
36471
|
const versionLabel = `${package_default.version}`;
|
|
36390
36472
|
const homeDir = homedir();
|
|
36391
36473
|
const startDir = process.env.SCHUB_CWD ?? process.cwd();
|
|
@@ -36425,14 +36507,20 @@ function App2({ copyToClipboard: copyToClipboard2 = copyToClipboard }) {
|
|
|
36425
36507
|
setMode((current) => current === "status" ? "plan" : "status");
|
|
36426
36508
|
}
|
|
36427
36509
|
});
|
|
36510
|
+
import_react28.default.useEffect(() => {
|
|
36511
|
+
if (mode !== "status") {
|
|
36512
|
+
setStatusShortcuts([]);
|
|
36513
|
+
}
|
|
36514
|
+
}, [mode]);
|
|
36428
36515
|
const handleCopyId = (value) => {
|
|
36429
36516
|
copyToClipboard2(value);
|
|
36430
36517
|
setCopyBanner(COPY_BANNER_TEXT);
|
|
36431
36518
|
};
|
|
36432
|
-
const
|
|
36519
|
+
const baseShortcuts = [
|
|
36433
36520
|
{ keyLabel: "o", label: "open file" },
|
|
36434
36521
|
{ keyLabel: "c", label: "copy" }
|
|
36435
36522
|
];
|
|
36523
|
+
const shortcuts = mode === "status" ? [...baseShortcuts, ...statusShortcuts] : baseShortcuts;
|
|
36436
36524
|
return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
36437
36525
|
backgroundColor: "black",
|
|
36438
36526
|
flexDirection: "column",
|
|
@@ -36490,7 +36578,8 @@ function App2({ copyToClipboard: copyToClipboard2 = copyToClipboard }) {
|
|
|
36490
36578
|
flexGrow: 1,
|
|
36491
36579
|
flexShrink: 1,
|
|
36492
36580
|
children: mode === "status" ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(StatusView, {
|
|
36493
|
-
onCopyId: handleCopyId
|
|
36581
|
+
onCopyId: handleCopyId,
|
|
36582
|
+
onShortcutsChange: setStatusShortcuts
|
|
36494
36583
|
}, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(PlanView, {
|
|
36495
36584
|
onCopyId: handleCopyId
|
|
36496
36585
|
}, undefined, false, undefined, this)
|
|
@@ -36563,7 +36652,7 @@ function App2({ copyToClipboard: copyToClipboard2 = copyToClipboard }) {
|
|
|
36563
36652
|
}
|
|
36564
36653
|
|
|
36565
36654
|
// src/commands/adr.ts
|
|
36566
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
36655
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
36567
36656
|
import { dirname as dirname6, join as join6 } from "node:path";
|
|
36568
36657
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
36569
36658
|
var BUNDLED_ADR_TEMPLATE_PATH = fileURLToPath3(new URL("../../templates/create-proposal/adr-template.md", import.meta.url));
|
|
@@ -36640,12 +36729,11 @@ var runAdrCreate = (args, startDir) => {
|
|
|
36640
36729
|
const templatePath = resolveTemplatePath(schubDir, join6("create-proposal", "adr-template.md"), BUNDLED_ADR_TEMPLATE_PATH);
|
|
36641
36730
|
const template = readFileSync5(templatePath, "utf8");
|
|
36642
36731
|
const rendered = renderAdrTemplate(template, adrTitle || summary.changeId);
|
|
36643
|
-
|
|
36732
|
+
mkdirSync4(dirname6(outputPath), { recursive: true });
|
|
36644
36733
|
writeFileSync3(outputPath, rendered, "utf8");
|
|
36645
36734
|
process.stdout.write(`[OK] Wrote ADR: ${outputPath}
|
|
36646
36735
|
`);
|
|
36647
36736
|
};
|
|
36648
|
-
|
|
36649
36737
|
// src/commands/changes.ts
|
|
36650
36738
|
var parseChangeCreateOptions = (args) => {
|
|
36651
36739
|
let changeId;
|
|
@@ -36771,6 +36859,72 @@ var parseChangeStatusOptions = (args) => {
|
|
|
36771
36859
|
const options = { changeId, status };
|
|
36772
36860
|
return options;
|
|
36773
36861
|
};
|
|
36862
|
+
var parseChangeArchiveOptions = (args) => {
|
|
36863
|
+
let changeId;
|
|
36864
|
+
let skipTasks = false;
|
|
36865
|
+
const unknown = [];
|
|
36866
|
+
const rejectUnsupported = (flag) => {
|
|
36867
|
+
throw new Error(`Unsupported option: ${flag}.`);
|
|
36868
|
+
};
|
|
36869
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
36870
|
+
const arg = args[index];
|
|
36871
|
+
if (arg === "--skip-tasks") {
|
|
36872
|
+
skipTasks = true;
|
|
36873
|
+
continue;
|
|
36874
|
+
}
|
|
36875
|
+
if (arg === "--change-id") {
|
|
36876
|
+
changeId = args[index + 1];
|
|
36877
|
+
if (changeId === undefined) {
|
|
36878
|
+
throw new Error("Missing value for --change-id.");
|
|
36879
|
+
}
|
|
36880
|
+
index += 1;
|
|
36881
|
+
continue;
|
|
36882
|
+
}
|
|
36883
|
+
if (arg.startsWith("--change-id=")) {
|
|
36884
|
+
changeId = arg.slice("--change-id=".length);
|
|
36885
|
+
continue;
|
|
36886
|
+
}
|
|
36887
|
+
if (arg === "--schub-root" || arg === "--agent-root") {
|
|
36888
|
+
rejectUnsupported(arg);
|
|
36889
|
+
}
|
|
36890
|
+
if (arg.startsWith("--schub-root=")) {
|
|
36891
|
+
rejectUnsupported("--schub-root");
|
|
36892
|
+
}
|
|
36893
|
+
if (arg.startsWith("--agent-root=")) {
|
|
36894
|
+
rejectUnsupported("--agent-root");
|
|
36895
|
+
}
|
|
36896
|
+
unknown.push(arg);
|
|
36897
|
+
}
|
|
36898
|
+
if (unknown.length > 0) {
|
|
36899
|
+
throw new Error(`Unknown option(s): ${unknown.join(", ")}`);
|
|
36900
|
+
}
|
|
36901
|
+
if (!changeId) {
|
|
36902
|
+
throw new Error("Provide --change-id.");
|
|
36903
|
+
}
|
|
36904
|
+
const options = { changeId, skipTasks };
|
|
36905
|
+
return options;
|
|
36906
|
+
};
|
|
36907
|
+
var parseChangeListOptions = (args) => {
|
|
36908
|
+
const unknown = [];
|
|
36909
|
+
const rejectUnsupported = (flag) => {
|
|
36910
|
+
throw new Error(`Unsupported option: ${flag}.`);
|
|
36911
|
+
};
|
|
36912
|
+
for (const arg of args) {
|
|
36913
|
+
if (arg === "--schub-root" || arg === "--agent-root") {
|
|
36914
|
+
rejectUnsupported(arg);
|
|
36915
|
+
}
|
|
36916
|
+
if (arg.startsWith("--schub-root=")) {
|
|
36917
|
+
rejectUnsupported("--schub-root");
|
|
36918
|
+
}
|
|
36919
|
+
if (arg.startsWith("--agent-root=")) {
|
|
36920
|
+
rejectUnsupported("--agent-root");
|
|
36921
|
+
}
|
|
36922
|
+
unknown.push(arg);
|
|
36923
|
+
}
|
|
36924
|
+
if (unknown.length > 0) {
|
|
36925
|
+
throw new Error(`Unknown option(s): ${unknown.join(", ")}`);
|
|
36926
|
+
}
|
|
36927
|
+
};
|
|
36774
36928
|
var runChangesCreate = (args, startDir) => {
|
|
36775
36929
|
const options = parseChangeCreateOptions(args);
|
|
36776
36930
|
const schubDir = resolveChangeRoot(startDir);
|
|
@@ -36785,9 +36939,27 @@ var runChangesStatus = (args, startDir) => {
|
|
|
36785
36939
|
process.stdout.write(`[OK] Updated status for ${updated.changeId}: ${updated.previousStatus} -> ${updated.status}
|
|
36786
36940
|
`);
|
|
36787
36941
|
};
|
|
36942
|
+
var runChangesArchive = (args, startDir) => {
|
|
36943
|
+
const options = parseChangeArchiveOptions(args);
|
|
36944
|
+
const schubDir = resolveChangeRoot(startDir);
|
|
36945
|
+
const archived = archiveChange(schubDir, options.changeId);
|
|
36946
|
+
const archivedTasks = options.skipTasks ? [] : archiveTasksForChange(schubDir, archived.changeId);
|
|
36947
|
+
const taskLabel = options.skipTasks ? "tasks kept" : `${archivedTasks.length} task${archivedTasks.length === 1 ? "" : "s"} archived`;
|
|
36948
|
+
process.stdout.write(`[OK] Archived change ${archived.changeId} (${taskLabel})
|
|
36949
|
+
`);
|
|
36950
|
+
};
|
|
36951
|
+
var runChangesList = (args, startDir) => {
|
|
36952
|
+
parseChangeListOptions(args);
|
|
36953
|
+
const schubDir = resolveChangeRoot(startDir);
|
|
36954
|
+
const changes = listChanges(schubDir);
|
|
36955
|
+
const lines = changes.map((change) => `${change.id} ${change.title} (${change.status})`);
|
|
36956
|
+
process.stdout.write(`${lines.join(`
|
|
36957
|
+
`)}
|
|
36958
|
+
`);
|
|
36959
|
+
};
|
|
36788
36960
|
|
|
36789
36961
|
// src/commands/cookbook.ts
|
|
36790
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
36962
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
36791
36963
|
import { dirname as dirname7, join as join7 } from "node:path";
|
|
36792
36964
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
36793
36965
|
var BUNDLED_COOKBOOK_TEMPLATE_PATH = fileURLToPath4(new URL("../../templates/create-proposal/cookbook-template.md", import.meta.url));
|
|
@@ -36850,14 +37022,14 @@ var runCookbookCreate = (args, startDir) => {
|
|
|
36850
37022
|
const templatePath = resolveTemplatePath(schubDir, join7("create-proposal", "cookbook-template.md"), BUNDLED_COOKBOOK_TEMPLATE_PATH);
|
|
36851
37023
|
const template = readFileSync6(templatePath, "utf8");
|
|
36852
37024
|
const rendered = renderCookbookTemplate(template, summary.changeTitle, summary.changeId);
|
|
36853
|
-
|
|
37025
|
+
mkdirSync5(dirname7(outputPath), { recursive: true });
|
|
36854
37026
|
writeFileSync4(outputPath, rendered, "utf8");
|
|
36855
37027
|
process.stdout.write(`[OK] Wrote cookbook: ${outputPath}
|
|
36856
37028
|
`);
|
|
36857
37029
|
};
|
|
36858
37030
|
|
|
36859
37031
|
// src/commands/eject.ts
|
|
36860
|
-
import { cpSync, existsSync as existsSync7, mkdirSync as
|
|
37032
|
+
import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync6, rmSync, statSync as statSync5 } from "node:fs";
|
|
36861
37033
|
import { join as join8 } from "node:path";
|
|
36862
37034
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
36863
37035
|
var BUNDLED_SKILLS_ROOT = fileURLToPath5(new URL("../../skills", import.meta.url));
|
|
@@ -36929,7 +37101,7 @@ Re-run with --force.`);
|
|
|
36929
37101
|
var runEject = (args, startDir) => {
|
|
36930
37102
|
const options = parseEjectOptions(args);
|
|
36931
37103
|
const schubRoot = resolveSchubRoot(startDir);
|
|
36932
|
-
|
|
37104
|
+
mkdirSync6(schubRoot, { recursive: true });
|
|
36933
37105
|
const skillsTarget = join8(schubRoot, "skills");
|
|
36934
37106
|
const templatesTarget = join8(schubRoot, "templates");
|
|
36935
37107
|
enforceOverwrite([skillsTarget, templatesTarget], options.force);
|
|
@@ -36942,7 +37114,7 @@ var runEject = (args, startDir) => {
|
|
|
36942
37114
|
};
|
|
36943
37115
|
|
|
36944
37116
|
// src/commands/init.ts
|
|
36945
|
-
import { cpSync as cpSync2, existsSync as existsSync8, mkdirSync as
|
|
37117
|
+
import { cpSync as cpSync2, existsSync as existsSync8, mkdirSync as mkdirSync8, readdirSync as readdirSync4, statSync as statSync6 } from "node:fs";
|
|
36946
37118
|
import { homedir as homedir2 } from "node:os";
|
|
36947
37119
|
import { join as join10, resolve as resolve5 } from "node:path";
|
|
36948
37120
|
import { createInterface } from "node:readline";
|
|
@@ -36950,7 +37122,7 @@ import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
|
36950
37122
|
|
|
36951
37123
|
// src/init.ts
|
|
36952
37124
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
36953
|
-
import { mkdirSync as
|
|
37125
|
+
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
36954
37126
|
import { join as join9, resolve as resolve4 } from "node:path";
|
|
36955
37127
|
var resolveGitRoot = (startDir) => {
|
|
36956
37128
|
const result = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
@@ -36969,12 +37141,15 @@ var initSchubRoot = (startDir = process.cwd()) => {
|
|
|
36969
37141
|
const gitRoot = resolveGitRoot(resolvedStart);
|
|
36970
37142
|
const root = gitRoot ?? resolvedStart;
|
|
36971
37143
|
const schubRoot = join9(root, ".schub");
|
|
36972
|
-
|
|
37144
|
+
mkdirSync7(schubRoot, { recursive: true });
|
|
36973
37145
|
return schubRoot;
|
|
36974
37146
|
};
|
|
36975
37147
|
|
|
36976
37148
|
// src/commands/init.ts
|
|
36977
|
-
var PROVIDERS = [
|
|
37149
|
+
var PROVIDERS = [
|
|
37150
|
+
{ id: "codex", label: "Codex" },
|
|
37151
|
+
{ id: "opencode", label: "Opencode" }
|
|
37152
|
+
];
|
|
36978
37153
|
var BUNDLED_SKILLS_ROOT2 = fileURLToPath6(new URL("../../skills", import.meta.url));
|
|
36979
37154
|
var isDirectory5 = (path) => {
|
|
36980
37155
|
try {
|
|
@@ -37028,8 +37203,21 @@ var resolveCodexSkillsRoot = (startDir) => {
|
|
|
37028
37203
|
const home = process.env.HOME ?? homedir2();
|
|
37029
37204
|
return join10(home, ".codex", "skills");
|
|
37030
37205
|
};
|
|
37031
|
-
var
|
|
37032
|
-
|
|
37206
|
+
var resolveOpencodeSkillsRoot = (startDir) => {
|
|
37207
|
+
const resolvedStart = resolve5(startDir);
|
|
37208
|
+
const gitRoot = resolveGitRoot(resolvedStart);
|
|
37209
|
+
if (!gitRoot) {
|
|
37210
|
+
return join10(resolvedStart, ".opencode", "skills");
|
|
37211
|
+
}
|
|
37212
|
+
const localOpencodeRoot = join10(gitRoot, ".opencode");
|
|
37213
|
+
if (isDirectory5(localOpencodeRoot)) {
|
|
37214
|
+
return join10(localOpencodeRoot, "skills");
|
|
37215
|
+
}
|
|
37216
|
+
const home = process.env.HOME ?? homedir2();
|
|
37217
|
+
return join10(home, ".opencode", "skills");
|
|
37218
|
+
};
|
|
37219
|
+
var installBundledSkills = (destination) => {
|
|
37220
|
+
mkdirSync8(destination, { recursive: true });
|
|
37033
37221
|
const entries = readdirSync4(BUNDLED_SKILLS_ROOT2, { withFileTypes: true });
|
|
37034
37222
|
const installed = [];
|
|
37035
37223
|
const skipped = [];
|
|
@@ -37048,8 +37236,10 @@ var installCodexSkills = (destination) => {
|
|
|
37048
37236
|
}
|
|
37049
37237
|
return { installed, skipped };
|
|
37050
37238
|
};
|
|
37051
|
-
var
|
|
37052
|
-
|
|
37239
|
+
var installCodexSkills = (destination) => installBundledSkills(destination);
|
|
37240
|
+
var installOpencodeSkills = (destination) => installBundledSkills(destination);
|
|
37241
|
+
var reportSkillsInstall = (label, destination, installed, skipped) => {
|
|
37242
|
+
process.stdout.write(`[OK] ${label} skills: ${destination}
|
|
37053
37243
|
`);
|
|
37054
37244
|
for (const skill of installed) {
|
|
37055
37245
|
process.stdout.write(`[OK] Installed ${skill}
|
|
@@ -37060,6 +37250,12 @@ var reportCodexInstall = (destination, installed, skipped) => {
|
|
|
37060
37250
|
`);
|
|
37061
37251
|
}
|
|
37062
37252
|
};
|
|
37253
|
+
var reportCodexInstall = (destination, installed, skipped) => {
|
|
37254
|
+
reportSkillsInstall("Codex", destination, installed, skipped);
|
|
37255
|
+
};
|
|
37256
|
+
var reportOpencodeInstall = (destination, installed, skipped) => {
|
|
37257
|
+
reportSkillsInstall("Opencode", destination, installed, skipped);
|
|
37258
|
+
};
|
|
37063
37259
|
var parseInitOptions = (args) => {
|
|
37064
37260
|
if (args.length === 0) {
|
|
37065
37261
|
return;
|
|
@@ -37077,11 +37273,16 @@ var runInit = async (args, startDir) => {
|
|
|
37077
37273
|
const { installed, skipped } = installCodexSkills(destination);
|
|
37078
37274
|
reportCodexInstall(destination, installed, skipped);
|
|
37079
37275
|
}
|
|
37276
|
+
if (providers.includes("opencode")) {
|
|
37277
|
+
const destination = resolveOpencodeSkillsRoot(startDir);
|
|
37278
|
+
const { installed, skipped } = installOpencodeSkills(destination);
|
|
37279
|
+
reportOpencodeInstall(destination, installed, skipped);
|
|
37280
|
+
}
|
|
37080
37281
|
};
|
|
37081
37282
|
|
|
37082
37283
|
// src/project.ts
|
|
37083
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
37084
|
-
import { basename as
|
|
37284
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
37285
|
+
import { basename as basename3, dirname as dirname8, join as join11, resolve as resolve6 } from "node:path";
|
|
37085
37286
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
37086
37287
|
var TEMPLATE_FILES = {
|
|
37087
37288
|
"project-overview.md": "project-overview-template.md",
|
|
@@ -37100,11 +37301,11 @@ var writeOutput = (path, content, overwrite) => {
|
|
|
37100
37301
|
if (existsSync9(path) && !overwrite) {
|
|
37101
37302
|
throw new Error(`[ERROR] Refusing to overwrite existing file: ${path}`);
|
|
37102
37303
|
}
|
|
37103
|
-
|
|
37304
|
+
mkdirSync9(dirname8(path), { recursive: true });
|
|
37104
37305
|
writeFileSync5(path, content, "utf8");
|
|
37105
37306
|
};
|
|
37106
37307
|
var deriveProjectName = (repoRoot) => {
|
|
37107
|
-
const name =
|
|
37308
|
+
const name = basename3(repoRoot).trim();
|
|
37108
37309
|
return name || "Project";
|
|
37109
37310
|
};
|
|
37110
37311
|
var resolveRepoRoot = (startDir, schubRoot, repoRoot) => {
|
|
@@ -37196,7 +37397,7 @@ var runProjectCreate = (args, startDir) => {
|
|
|
37196
37397
|
};
|
|
37197
37398
|
|
|
37198
37399
|
// src/commands/review.ts
|
|
37199
|
-
import { existsSync as existsSync10, mkdirSync as
|
|
37400
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
37200
37401
|
import { dirname as dirname9, join as join12, resolve as resolve7 } from "node:path";
|
|
37201
37402
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
37202
37403
|
var BUNDLED_REVIEW_TEMPLATE_PATH = fileURLToPath8(new URL("../../templates/review-proposal/review-me-template.md", import.meta.url));
|
|
@@ -37330,7 +37531,7 @@ var runReviewCreate = (args, startDir) => {
|
|
|
37330
37531
|
const schubDir = resolveChangeRoot(startDir);
|
|
37331
37532
|
const trimmedId = options.changeId.trim();
|
|
37332
37533
|
if (!isValidChangeId(trimmedId)) {
|
|
37333
|
-
throw new Error(`Invalid change-id '${options.changeId}'. Use kebab-case or a C-prefixed id (e.g.,
|
|
37534
|
+
throw new Error(`Invalid change-id '${options.changeId}'. Use kebab-case or a C-prefixed id (e.g., C0001_add-user-auth).`);
|
|
37334
37535
|
}
|
|
37335
37536
|
const changeId = normalizeChangeId(trimmedId);
|
|
37336
37537
|
const changeTitle = options.title?.trim() || readChangeSummary(schubDir, changeId).changeTitle;
|
|
@@ -37338,7 +37539,7 @@ var runReviewCreate = (args, startDir) => {
|
|
|
37338
37539
|
if (existsSync10(outputPath) && !options.overwrite) {
|
|
37339
37540
|
throw new Error(`Refusing to overwrite existing file: ${outputPath}`);
|
|
37340
37541
|
}
|
|
37341
|
-
|
|
37542
|
+
mkdirSync10(dirname9(outputPath), { recursive: true });
|
|
37342
37543
|
const templatePath = resolveTemplatePath(schubDir, join12("review-proposal", "review-me-template.md"), BUNDLED_REVIEW_TEMPLATE_PATH);
|
|
37343
37544
|
const template = readFileSync8(templatePath, "utf8");
|
|
37344
37545
|
const rendered = renderChangeTemplate(template, changeTitle || changeId, changeId);
|
|
@@ -37533,6 +37734,8 @@ var HELP_TEXT = `schub [command]
|
|
|
37533
37734
|
Commands:
|
|
37534
37735
|
changes create Create a change proposal
|
|
37535
37736
|
changes status Update change proposal status
|
|
37737
|
+
changes archive Archive a change proposal
|
|
37738
|
+
changes list List change proposals
|
|
37536
37739
|
project create Create project docs
|
|
37537
37740
|
tasks create Create task files for a change
|
|
37538
37741
|
tasks list List tasks
|
|
@@ -37582,6 +37785,14 @@ var runCommand = async () => {
|
|
|
37582
37785
|
runChangesStatus(rest, getStartDir());
|
|
37583
37786
|
return;
|
|
37584
37787
|
}
|
|
37788
|
+
if (secondary === "archive") {
|
|
37789
|
+
runChangesArchive(rest, getStartDir());
|
|
37790
|
+
return;
|
|
37791
|
+
}
|
|
37792
|
+
if (secondary === "list") {
|
|
37793
|
+
runChangesList(rest, getStartDir());
|
|
37794
|
+
return;
|
|
37795
|
+
}
|
|
37585
37796
|
break;
|
|
37586
37797
|
case "project":
|
|
37587
37798
|
if (secondary === "create") {
|