@tablation/crew 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +65 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -211,7 +211,7 @@ crew pause|resume [route] [R] pause everything, or one role
|
|
|
211
211
|
crew log [route] tail the log
|
|
212
212
|
crew inbox [--member NAME] your tickets across every workspace
|
|
213
213
|
crew connect WS[/PROJECT] resolve a workspace/project's ids into the state tree
|
|
214
|
-
crew repos add ROUTE PATH
|
|
214
|
+
crew repos add [ROUTE] [NAME] [PATH] attach a local checkout to a route in crew.yaml, creating its Repos row if missing (--dry-run previews)
|
|
215
215
|
crew repos list ROUTE the checkouts a route has, and the tracker Repos row each matches
|
|
216
216
|
crew install write and load this platform's scheduler unit
|
|
217
217
|
crew uninstall unload and remove it
|
package/dist/cli.js
CHANGED
|
@@ -2793,6 +2793,12 @@ function gitOk(cwd, args) {
|
|
|
2793
2793
|
return null;
|
|
2794
2794
|
}
|
|
2795
2795
|
}
|
|
2796
|
+
function commitDate(cwd, sha) {
|
|
2797
|
+
const out = gitOk(cwd, ["show", "-s", "--format=%cI", sha]);
|
|
2798
|
+
if (!out) return null;
|
|
2799
|
+
const t = new Date(out);
|
|
2800
|
+
return Number.isNaN(t.getTime()) ? null : t.toISOString();
|
|
2801
|
+
}
|
|
2796
2802
|
var currentBranch = (cwd) => git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
2797
2803
|
var headSha = (cwd) => git(cwd, ["rev-parse", "HEAD"]);
|
|
2798
2804
|
function status(cwd) {
|
|
@@ -4980,7 +4986,10 @@ function planStamp(cwd, tickets, contract, fromSha, toSha, merged = /* @__PURE__
|
|
|
4980
4986
|
return null;
|
|
4981
4987
|
}).filter((p) => p !== null);
|
|
4982
4988
|
}
|
|
4983
|
-
|
|
4989
|
+
function mergedAtFor(cwd, sha, fallback) {
|
|
4990
|
+
return cwd && commitDate(cwd, sha) || fallback;
|
|
4991
|
+
}
|
|
4992
|
+
async function applyStamp(tracker, plan, version, contract, emit, dryRun, cwd) {
|
|
4984
4993
|
let stamped = 0;
|
|
4985
4994
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
4986
4995
|
for (const { ticket, sha } of plan) {
|
|
@@ -5000,10 +5009,9 @@ async function applyStamp(tracker, plan, version, contract, emit, dryRun) {
|
|
|
5000
5009
|
...version ? { released_version: version } : {},
|
|
5001
5010
|
// Written here rather than only on the external path (below): a
|
|
5002
5011
|
// ticket's record should mean the same thing regardless of which
|
|
5003
|
-
// release mode closed it (ISSUE-218). `merged_at` is
|
|
5004
|
-
//
|
|
5005
|
-
|
|
5006
|
-
...sha ? { commit_sha: sha, merged_at: at } : {}
|
|
5012
|
+
// release mode closed it (ISSUE-218). `merged_at` is the commit's own
|
|
5013
|
+
// date, not the stamp time (which trails the deploy in ci_* modes).
|
|
5014
|
+
...sha ? { commit_sha: sha, merged_at: mergedAtFor(cwd, sha, at) } : {}
|
|
5007
5015
|
});
|
|
5008
5016
|
emit.emit(`stamped ${contract.statuses.deployed}${version ? ` (${version})` : ""}${shaNote}`, {
|
|
5009
5017
|
ticket: ticket.issue_id
|
|
@@ -5015,7 +5023,7 @@ async function applyStamp(tracker, plan, version, contract, emit, dryRun) {
|
|
|
5015
5023
|
}
|
|
5016
5024
|
return stamped;
|
|
5017
5025
|
}
|
|
5018
|
-
async function applyExternalClosures(tracker, closures, contract, emit, dryRun) {
|
|
5026
|
+
async function applyExternalClosures(tracker, closures, contract, emit, dryRun, cwd) {
|
|
5019
5027
|
if (!closures?.length) return 0;
|
|
5020
5028
|
let stamped = 0;
|
|
5021
5029
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5030,7 +5038,7 @@ async function applyExternalClosures(tracker, closures, contract, emit, dryRun)
|
|
|
5030
5038
|
try {
|
|
5031
5039
|
await tracker.updateTicket(ticket.id, {
|
|
5032
5040
|
commit_sha: closure.mergedAt,
|
|
5033
|
-
merged_at: at,
|
|
5041
|
+
merged_at: mergedAtFor(cwd, closure.mergedAt, at),
|
|
5034
5042
|
...confirmed ? { status: contract.statuses.deployed, released_at: at, ...version ? { released_version: version } : {} } : {}
|
|
5035
5043
|
});
|
|
5036
5044
|
emit.emit(`stamped commit_sha (${closure.mergedAt.slice(0, 8)})${shippedNote}`, { ticket: ticket.issue_id });
|
|
@@ -7034,6 +7042,27 @@ function matchRepoName(origin, resolved) {
|
|
|
7034
7042
|
}
|
|
7035
7043
|
return void 0;
|
|
7036
7044
|
}
|
|
7045
|
+
function routeNamesOf(text) {
|
|
7046
|
+
const routes = parseDocument(text).get("routes", true);
|
|
7047
|
+
if (!isSeq(routes)) return [];
|
|
7048
|
+
return routes.items.flatMap((i) => isMap(i) && typeof i.get("route") === "string" ? [i.get("route")] : []);
|
|
7049
|
+
}
|
|
7050
|
+
function parseAddArgs(args, routes, isDir) {
|
|
7051
|
+
const rest = [...args];
|
|
7052
|
+
let route;
|
|
7053
|
+
if (rest.length > 0 && routes.includes(rest[0])) route = rest.shift();
|
|
7054
|
+
else if (rest.length > 0 && /^[^/\s]+\/[^/\s]+$/.test(rest[0]) && !isDir(rest[0])) {
|
|
7055
|
+
throw new ReposError(`no route "${rest[0]}" in the config (have: ${routes.join(", ") || "none"})`);
|
|
7056
|
+
}
|
|
7057
|
+
if (!route) {
|
|
7058
|
+
if (routes.length === 1) route = routes[0];
|
|
7059
|
+
else throw new ReposError(routes.length === 0 ? "the config has no routes \u2014 run `crew connect` first" : `several routes are configured; name one: ${routes.join(", ")}`);
|
|
7060
|
+
}
|
|
7061
|
+
if (rest.length > 2) throw new ReposError("too many arguments: expected [route] [name] [path]");
|
|
7062
|
+
if (rest.length === 2) return { route, name: rest[0], path: rest[1] };
|
|
7063
|
+
if (rest.length === 1) return isDir(rest[0]) ? { route, path: rest[0] } : { route, name: rest[0], path: "." };
|
|
7064
|
+
return { route, path: "." };
|
|
7065
|
+
}
|
|
7037
7066
|
function expandDir(p, base) {
|
|
7038
7067
|
const home = p === "~" ? homedir3() : p.startsWith("~/") ? `${homedir3()}/${p.slice(2)}` : p;
|
|
7039
7068
|
return resolve3(base, home);
|
|
@@ -7073,7 +7102,7 @@ function planRepoAdd(i) {
|
|
|
7073
7102
|
throw new ReposError(`${target} is already configured on route ${i.route}`);
|
|
7074
7103
|
}
|
|
7075
7104
|
const matched = matchRepoName(i.origin, i.resolved);
|
|
7076
|
-
const name = matched ?? i.name ?? basename5(target);
|
|
7105
|
+
const name = matched ?? i.name ?? normalizeRemote(i.origin)?.split("/").pop() ?? basename5(target);
|
|
7077
7106
|
let warning;
|
|
7078
7107
|
if (!matched) {
|
|
7079
7108
|
const known = Object.values(i.resolved?.repoNames ?? {});
|
|
@@ -8319,7 +8348,7 @@ function usage() {
|
|
|
8319
8348
|
crew agents sync [route] [--force] push crew's personas AND skill files into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona)
|
|
8320
8349
|
crew agents prompt R [route] print one persona's current prompt (e.g. R=pair, for a SessionStart hook)
|
|
8321
8350
|
crew skills sync [route] push only crew's skill files (e.g. grill-me) into the workspace Agent Skills table
|
|
8322
|
-
crew repos add ROUTE PATH
|
|
8351
|
+
crew repos add [ROUTE] [NAME] [PATH] attach a local checkout to a route in crew.yaml (--dry-run previews)
|
|
8323
8352
|
crew repos list ROUTE the checkouts a route has, with the tracker's Repos row each matches
|
|
8324
8353
|
crew logbook list [route] recent Agent Log entries, filterable by --role/--ticket
|
|
8325
8354
|
crew logbook show [route] ID [--prompt] one entry; --prompt reconstructs and verifies its prompt
|
|
@@ -8822,12 +8851,12 @@ ${changed2.map((c) => ` - ${c}
|
|
|
8822
8851
|
outcome.decision.head,
|
|
8823
8852
|
new Map(outcome.merged.map((m) => [m.ticket.issue_id, m.sha]))
|
|
8824
8853
|
);
|
|
8825
|
-
if (plan.length) await applyStamp(tracker, plan, outcome.version, tracker.contract, remit, dryRun);
|
|
8854
|
+
if (plan.length) await applyStamp(tracker, plan, outcome.version, tracker.contract, remit, dryRun, target.dir);
|
|
8826
8855
|
} else if (outcome.stopped) {
|
|
8827
8856
|
remit.emit(`nothing stamped \u2014 ${outcome.stopped}`);
|
|
8828
8857
|
}
|
|
8829
8858
|
if (outcome.externalClosures?.length) {
|
|
8830
|
-
await applyExternalClosures(tracker, outcome.externalClosures, tracker.contract, remit, dryRun);
|
|
8859
|
+
await applyExternalClosures(tracker, outcome.externalClosures, tracker.contract, remit, dryRun, target.dir);
|
|
8831
8860
|
}
|
|
8832
8861
|
const news = describeRelease(outcome, scope);
|
|
8833
8862
|
if (news) {
|
|
@@ -9605,12 +9634,31 @@ ${changed2.map((c) => ` - ${c}
|
|
|
9605
9634
|
}
|
|
9606
9635
|
case "repos": {
|
|
9607
9636
|
const sub = positional[1];
|
|
9608
|
-
const routeName = positional[2];
|
|
9609
9637
|
const usage2 = () => {
|
|
9610
|
-
process.stderr.write("crew repos add
|
|
9638
|
+
process.stderr.write("crew repos add [workspace/project] [name] [path] [--name N] [--project P] [--dry-run] [--no-create]\ncrew repos list <workspace/project>\n");
|
|
9611
9639
|
process.exit(2);
|
|
9612
9640
|
};
|
|
9613
|
-
if (sub !== "add" && sub !== "list"
|
|
9641
|
+
if (sub !== "add" && sub !== "list") usage2();
|
|
9642
|
+
const configText = readFileSync12(cfg.configFile, "utf8");
|
|
9643
|
+
let routeName = positional[2];
|
|
9644
|
+
let addPath = ".";
|
|
9645
|
+
let addName;
|
|
9646
|
+
if (sub === "list" && !routeName) usage2();
|
|
9647
|
+
if (sub === "add") {
|
|
9648
|
+
try {
|
|
9649
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync18(resolvePath(p.replace(/^~(?=\/|$)/, homedir4()))));
|
|
9650
|
+
routeName = a.route;
|
|
9651
|
+
addPath = a.path;
|
|
9652
|
+
addName = a.name;
|
|
9653
|
+
} catch (e) {
|
|
9654
|
+
if (e instanceof ReposError) {
|
|
9655
|
+
process.stderr.write(`crew repos: ${e.message}
|
|
9656
|
+
`);
|
|
9657
|
+
process.exit(2);
|
|
9658
|
+
}
|
|
9659
|
+
throw e;
|
|
9660
|
+
}
|
|
9661
|
+
}
|
|
9614
9662
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
9615
9663
|
let resolvedRemotes;
|
|
9616
9664
|
if (existsSync18(resolvedFile)) {
|
|
@@ -9620,7 +9668,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
9620
9668
|
}
|
|
9621
9669
|
}
|
|
9622
9670
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
9623
|
-
const text =
|
|
9671
|
+
const text = configText;
|
|
9624
9672
|
const base = dirname9(cfg.configFile);
|
|
9625
9673
|
try {
|
|
9626
9674
|
if (sub === "list") {
|
|
@@ -9636,7 +9684,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
9636
9684
|
}
|
|
9637
9685
|
break;
|
|
9638
9686
|
}
|
|
9639
|
-
const target = resolvePath(
|
|
9687
|
+
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir4()));
|
|
9640
9688
|
const top = existsSync18(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
9641
9689
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
9642
9690
|
const origin = originOf(top);
|
|
@@ -9647,7 +9695,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
9647
9695
|
path: top,
|
|
9648
9696
|
origin,
|
|
9649
9697
|
resolved: resolvedRemotes,
|
|
9650
|
-
name: value("name"),
|
|
9698
|
+
name: value("name") ?? addName,
|
|
9651
9699
|
nameForExisting: (d) => matchRepoName(originOf(d), resolvedRemotes)
|
|
9652
9700
|
});
|
|
9653
9701
|
let rowTracker;
|
package/package.json
CHANGED