@staff0rd/assist 0.615.3 → 0.616.1
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 +2 -1
- package/dist/commands/sessions/web/bundle.js +287 -287
- package/dist/index.js +261 -130
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@staff0rd/assist",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.616.1",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -16541,6 +16541,84 @@ function stripEnvPrefix(parts) {
|
|
|
16541
16541
|
return i > 0 ? parts.slice(i) : parts;
|
|
16542
16542
|
}
|
|
16543
16543
|
|
|
16544
|
+
// src/shared/extractGhApiMethod.ts
|
|
16545
|
+
function extractGhApiMethod(args) {
|
|
16546
|
+
for (let i = 0; i < args.length; i++) {
|
|
16547
|
+
const arg = args[i];
|
|
16548
|
+
if (arg.startsWith("--method=")) return arg.slice("--method=".length);
|
|
16549
|
+
if (arg.startsWith("-X=")) return arg.slice("-X=".length);
|
|
16550
|
+
if (arg.startsWith("-X") && arg.length > 2) return arg.slice(2);
|
|
16551
|
+
if ((arg === "--method" || arg === "-X") && i + 1 < args.length) {
|
|
16552
|
+
return args[i + 1];
|
|
16553
|
+
}
|
|
16554
|
+
}
|
|
16555
|
+
return void 0;
|
|
16556
|
+
}
|
|
16557
|
+
|
|
16558
|
+
// src/shared/tokenize.ts
|
|
16559
|
+
function tokenize(command) {
|
|
16560
|
+
const tokens = [];
|
|
16561
|
+
let current = "";
|
|
16562
|
+
let inSingle = false;
|
|
16563
|
+
let inDouble = false;
|
|
16564
|
+
for (let i = 0; i < command.length; i++) {
|
|
16565
|
+
const ch = command[i];
|
|
16566
|
+
if (ch === "'" && !inDouble) {
|
|
16567
|
+
inSingle = !inSingle;
|
|
16568
|
+
current += ch;
|
|
16569
|
+
} else if (ch === '"' && !inSingle) {
|
|
16570
|
+
inDouble = !inDouble;
|
|
16571
|
+
current += ch;
|
|
16572
|
+
} else if (ch === "\\" && inDouble && i + 1 < command.length) {
|
|
16573
|
+
current += ch + command[++i];
|
|
16574
|
+
} else if (/\s/.test(ch) && !inSingle && !inDouble) {
|
|
16575
|
+
if (current) {
|
|
16576
|
+
tokens.push(current);
|
|
16577
|
+
current = "";
|
|
16578
|
+
}
|
|
16579
|
+
} else {
|
|
16580
|
+
current += ch;
|
|
16581
|
+
}
|
|
16582
|
+
}
|
|
16583
|
+
if (current) tokens.push(current);
|
|
16584
|
+
return tokens;
|
|
16585
|
+
}
|
|
16586
|
+
|
|
16587
|
+
// src/commands/cliHook/isGhIssueApiWrite.ts
|
|
16588
|
+
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PATCH", "PUT", "DELETE"]);
|
|
16589
|
+
var BODY_FLAGS = ["-f", "-F", "--field", "--raw-field", "--input"];
|
|
16590
|
+
var COMMAND_OPERATORS = /* @__PURE__ */ new Set(["&&", "||", ";", "|", "&"]);
|
|
16591
|
+
var ISSUE_ENDPOINT = /(?:^|[/"'`])repos\/[^/\s"'`]+\/[^/\s"'`]+\/issues(?:$|[/?"'`])/;
|
|
16592
|
+
function isGhIssueApiWrite(command) {
|
|
16593
|
+
const tokens = tokenize(command);
|
|
16594
|
+
for (let i = 0; i + 1 < tokens.length; i++) {
|
|
16595
|
+
if (tokens[i] !== "gh" || tokens[i + 1] !== "api") continue;
|
|
16596
|
+
const args = argsUntilNextCommand(tokens.slice(i + 2));
|
|
16597
|
+
if (targetsIssueEndpoint(args) && isWrite(args)) return true;
|
|
16598
|
+
}
|
|
16599
|
+
return false;
|
|
16600
|
+
}
|
|
16601
|
+
function argsUntilNextCommand(tokens) {
|
|
16602
|
+
const end = tokens.findIndex((token) => COMMAND_OPERATORS.has(token));
|
|
16603
|
+
return end === -1 ? tokens : tokens.slice(0, end);
|
|
16604
|
+
}
|
|
16605
|
+
function targetsIssueEndpoint(args) {
|
|
16606
|
+
return args.some((arg) => ISSUE_ENDPOINT.test(arg));
|
|
16607
|
+
}
|
|
16608
|
+
function isWrite(args) {
|
|
16609
|
+
const method = extractGhApiMethod(args);
|
|
16610
|
+
if (method) return WRITE_METHODS.has(unquote(method).toUpperCase());
|
|
16611
|
+
return args.some(isBodyFlag);
|
|
16612
|
+
}
|
|
16613
|
+
function isBodyFlag(arg) {
|
|
16614
|
+
return BODY_FLAGS.some(
|
|
16615
|
+
(flag) => arg === flag || arg.startsWith(`${flag}=`) || flag.length === 2 && arg.length > 2 && arg.startsWith(flag)
|
|
16616
|
+
);
|
|
16617
|
+
}
|
|
16618
|
+
function unquote(value) {
|
|
16619
|
+
return value.replace(/^['"]|['"]$/g, "");
|
|
16620
|
+
}
|
|
16621
|
+
|
|
16544
16622
|
// src/commands/cliHook/findBuiltinDeny.ts
|
|
16545
16623
|
var BUILTIN_DENIES = [
|
|
16546
16624
|
{
|
|
@@ -16555,6 +16633,10 @@ var BUILTIN_DENIES = [
|
|
|
16555
16633
|
pattern: "gh issue create",
|
|
16556
16634
|
message: "Do not run 'gh issue create' directly. Use 'assist github issue create --title <title> --body <body> [-R <owner>/<repo>]' instead \u2014 it validates the title and body before delegating to gh, and gates the issue on the user's approval itself. Run 'assist github issue create --help' and follow its guidance to compose the body; it is authoritative on how approval is handled in this environment."
|
|
16557
16635
|
},
|
|
16636
|
+
{
|
|
16637
|
+
pattern: "gh issue edit",
|
|
16638
|
+
message: "Do not run 'gh issue edit' directly. Use 'assist github issue edit <number> [-R <owner>/<repo>]' instead \u2014 it fetches the issue's current body, opens it in the assist web preview pane, and pushes nothing back until the user approves it. Run 'assist github issue edit --help' for how the working file and the preview pane fit together; it is authoritative on how approval is handled in this environment."
|
|
16639
|
+
},
|
|
16558
16640
|
{
|
|
16559
16641
|
pattern: "gh issue comment",
|
|
16560
16642
|
message: "Do not run 'gh issue comment' directly. Use 'assist github issue comment <number> --body <body> [-R <owner>/<repo>]' instead \u2014 it validates the body before delegating to gh, and gates the comment on the user's approval itself. Run 'assist github issue comment --help' and follow its guidance to compose the body; it is authoritative on how approval is handled in this environment."
|
|
@@ -16564,6 +16646,7 @@ var BUILTIN_DENIES = [
|
|
|
16564
16646
|
message: `Do not run 'git commit' directly. Use 'assist commit "<message>"' instead.`
|
|
16565
16647
|
}
|
|
16566
16648
|
];
|
|
16649
|
+
var GH_ISSUE_API_MESSAGE = "Do not write to GitHub issue endpoints with 'gh api' \u2014 it bypasses the content validation and the approval pane that every outward-facing issue write goes through. Use 'assist github issue create --title <title> --body <body>' to open an issue, 'assist github issue edit <number>' to rework one's body, 'assist github issue comment <number> --body <body>' to post a comment, or 'assist github issue edit-comment <comment-id> --body <body>' to amend a comment that is already posted. Each validates the content and gates it on the user's approval before anything reaches GitHub; run the command's --help for how to compose it.";
|
|
16567
16650
|
var BRANCH_CREATION_MESSAGE = "Do not create branches with raw git. Use the /branch command, or 'assist branch <slug> [--jira <KEY>]' \u2014 it branches off the fresh remote default and enforces the team naming convention.";
|
|
16568
16651
|
var COMMAND_BOUNDARY = String.raw`(?<=(?:^|[;&|(\n])\s*)`;
|
|
16569
16652
|
var BRANCH_CREATION_REGEXES = [
|
|
@@ -16580,17 +16663,24 @@ function rawDenyRegex(pattern2) {
|
|
|
16580
16663
|
return new RegExp(`(?<=^|\\s)${tokens}(?=\\s|$)`);
|
|
16581
16664
|
}
|
|
16582
16665
|
var RAW_BUILTIN_DENIES = [
|
|
16583
|
-
...BUILTIN_DENIES.map((rule) =>
|
|
16584
|
-
|
|
16585
|
-
|
|
16586
|
-
|
|
16666
|
+
...BUILTIN_DENIES.map((rule) => {
|
|
16667
|
+
const regex = rawDenyRegex(rule.pattern);
|
|
16668
|
+
return {
|
|
16669
|
+
message: rule.message,
|
|
16670
|
+
matches: (text18) => regex.test(text18)
|
|
16671
|
+
};
|
|
16672
|
+
}),
|
|
16587
16673
|
...BRANCH_CREATION_REGEXES.map((regex) => ({
|
|
16588
16674
|
message: BRANCH_CREATION_MESSAGE,
|
|
16589
|
-
regex
|
|
16590
|
-
}))
|
|
16675
|
+
matches: (text18) => regex.test(text18)
|
|
16676
|
+
})),
|
|
16677
|
+
{
|
|
16678
|
+
message: GH_ISSUE_API_MESSAGE,
|
|
16679
|
+
matches: isGhIssueApiWrite
|
|
16680
|
+
}
|
|
16591
16681
|
];
|
|
16592
16682
|
function matchBuiltinDeny(text18) {
|
|
16593
|
-
return RAW_BUILTIN_DENIES.find((rule) => rule.
|
|
16683
|
+
return RAW_BUILTIN_DENIES.find((rule) => rule.matches(text18));
|
|
16594
16684
|
}
|
|
16595
16685
|
function toDecision(rule) {
|
|
16596
16686
|
if (!rule) return void 0;
|
|
@@ -16610,6 +16700,64 @@ function findBuiltinDenyRaw(rawCommand) {
|
|
|
16610
16700
|
return toDecision(matchBuiltinDeny(rawCommand));
|
|
16611
16701
|
}
|
|
16612
16702
|
|
|
16703
|
+
// src/commands/cliHook/findTruncatedReadDeny.ts
|
|
16704
|
+
var UNTRUNCATABLE_READS = ["assist backlog show", "assist backlog view"];
|
|
16705
|
+
var APPROVAL_GATED_COMMANDS = [
|
|
16706
|
+
"assist backlog propose",
|
|
16707
|
+
"assist backlog comment",
|
|
16708
|
+
"assist backlog update-plan",
|
|
16709
|
+
"assist backlog add-phase",
|
|
16710
|
+
"assist github issue create",
|
|
16711
|
+
"assist github issue edit",
|
|
16712
|
+
"assist github issue comment",
|
|
16713
|
+
"assist slack post",
|
|
16714
|
+
"assist prs raise",
|
|
16715
|
+
"assist prs edit",
|
|
16716
|
+
"assist prs comment",
|
|
16717
|
+
"assist prs reply",
|
|
16718
|
+
"assist prs wontfix",
|
|
16719
|
+
"assist miro extract"
|
|
16720
|
+
];
|
|
16721
|
+
var TRUNCATOR_BINARIES = ["head", "tail"];
|
|
16722
|
+
var PIPED_TRUNCATOR_RE = /\|\s*(?:\S*\/)?(?:head|tail)\b/;
|
|
16723
|
+
function matchPrefix(prefixes, part) {
|
|
16724
|
+
return prefixes.find(
|
|
16725
|
+
(prefix2) => part === prefix2 || part.startsWith(`${prefix2} `)
|
|
16726
|
+
);
|
|
16727
|
+
}
|
|
16728
|
+
function startsWithTruncator(part) {
|
|
16729
|
+
const binary = part.split(/\s+/)[0]?.split("/").pop() ?? "";
|
|
16730
|
+
return TRUNCATOR_BINARIES.includes(binary);
|
|
16731
|
+
}
|
|
16732
|
+
function toDecision2(read3, gated) {
|
|
16733
|
+
if (read3)
|
|
16734
|
+
return {
|
|
16735
|
+
permissionDecision: "deny",
|
|
16736
|
+
permissionDecisionReason: `Do not pipe '${read3}' through head or tail. Plan, Activity and Comments print at the end of the output, so a truncated read drops them and leaves you assuming the item has none. Run '${read3} <id>' bare and read all of it, or use a focused view: 'assist backlog comments <id>' for comments only.`
|
|
16737
|
+
};
|
|
16738
|
+
if (gated)
|
|
16739
|
+
return {
|
|
16740
|
+
permissionDecision: "deny",
|
|
16741
|
+
permissionDecisionReason: `Do not pipe '${gated}' through head or tail. It gates on a preview the reviewer can reject with inline comments, and those comments print at the end of the output. Nothing persists them, so a truncated read discards the reviewer's feedback for good and they have to retype it. Run '${gated}' bare and read all of it.`
|
|
16742
|
+
};
|
|
16743
|
+
return void 0;
|
|
16744
|
+
}
|
|
16745
|
+
function findTruncatedReadDeny(parts) {
|
|
16746
|
+
if (!parts.some(startsWithTruncator)) return void 0;
|
|
16747
|
+
return toDecision2(
|
|
16748
|
+
parts.map((part) => matchPrefix(UNTRUNCATABLE_READS, part)).find(Boolean),
|
|
16749
|
+
parts.map((part) => matchPrefix(APPROVAL_GATED_COMMANDS, part)).find(Boolean)
|
|
16750
|
+
);
|
|
16751
|
+
}
|
|
16752
|
+
function findTruncatedReadDenyRaw(rawCommand) {
|
|
16753
|
+
if (!startsWithTruncator(rawCommand) && !PIPED_TRUNCATOR_RE.test(rawCommand))
|
|
16754
|
+
return void 0;
|
|
16755
|
+
return toDecision2(
|
|
16756
|
+
matchPrefix(UNTRUNCATABLE_READS, rawCommand),
|
|
16757
|
+
matchPrefix(APPROVAL_GATED_COMMANDS, rawCommand)
|
|
16758
|
+
);
|
|
16759
|
+
}
|
|
16760
|
+
|
|
16613
16761
|
// src/shared/isApprovedRead.ts
|
|
16614
16762
|
import { resolve as resolve13, sep } from "path";
|
|
16615
16763
|
|
|
@@ -16619,38 +16767,9 @@ function appliesFixStructure(command) {
|
|
|
16619
16767
|
return APPLY_RE.test(command);
|
|
16620
16768
|
}
|
|
16621
16769
|
|
|
16622
|
-
// src/shared/tokenize.ts
|
|
16623
|
-
function tokenize(command) {
|
|
16624
|
-
const tokens = [];
|
|
16625
|
-
let current = "";
|
|
16626
|
-
let inSingle = false;
|
|
16627
|
-
let inDouble = false;
|
|
16628
|
-
for (let i = 0; i < command.length; i++) {
|
|
16629
|
-
const ch = command[i];
|
|
16630
|
-
if (ch === "'" && !inDouble) {
|
|
16631
|
-
inSingle = !inSingle;
|
|
16632
|
-
current += ch;
|
|
16633
|
-
} else if (ch === '"' && !inSingle) {
|
|
16634
|
-
inDouble = !inDouble;
|
|
16635
|
-
current += ch;
|
|
16636
|
-
} else if (ch === "\\" && inDouble && i + 1 < command.length) {
|
|
16637
|
-
current += ch + command[++i];
|
|
16638
|
-
} else if (/\s/.test(ch) && !inSingle && !inDouble) {
|
|
16639
|
-
if (current) {
|
|
16640
|
-
tokens.push(current);
|
|
16641
|
-
current = "";
|
|
16642
|
-
}
|
|
16643
|
-
} else {
|
|
16644
|
-
current += ch;
|
|
16645
|
-
}
|
|
16646
|
-
}
|
|
16647
|
-
if (current) tokens.push(current);
|
|
16648
|
-
return tokens;
|
|
16649
|
-
}
|
|
16650
|
-
|
|
16651
16770
|
// src/shared/isGhApiRead.ts
|
|
16652
16771
|
var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
16653
|
-
var
|
|
16772
|
+
var BODY_FLAGS2 = /* @__PURE__ */ new Set(["-f", "-F", "--field", "--raw-field", "--input"]);
|
|
16654
16773
|
var FIELD_FLAGS = /* @__PURE__ */ new Set(["-f", "--field", "-F"]);
|
|
16655
16774
|
function isGhApiRead(command) {
|
|
16656
16775
|
const tokens = tokenize(command);
|
|
@@ -16661,24 +16780,13 @@ function isGhApiRead(command) {
|
|
|
16661
16780
|
if (args.includes("graphql")) {
|
|
16662
16781
|
return isGraphqlRead(args);
|
|
16663
16782
|
}
|
|
16664
|
-
const method =
|
|
16783
|
+
const method = extractGhApiMethod(args);
|
|
16665
16784
|
if (method) return READ_METHODS.has(method.toUpperCase());
|
|
16666
|
-
if (args.some((t) =>
|
|
16785
|
+
if (args.some((t) => BODY_FLAGS2.has(t))) return false;
|
|
16667
16786
|
return true;
|
|
16668
16787
|
}
|
|
16669
|
-
function extractMethod(args) {
|
|
16670
|
-
for (let i = 0; i < args.length; i++) {
|
|
16671
|
-
const arg = args[i];
|
|
16672
|
-
if (arg.startsWith("--method=")) return arg.slice("--method=".length);
|
|
16673
|
-
if (arg.startsWith("-X=")) return arg.slice("-X=".length);
|
|
16674
|
-
if ((arg === "--method" || arg === "-X") && i + 1 < args.length) {
|
|
16675
|
-
return args[i + 1];
|
|
16676
|
-
}
|
|
16677
|
-
}
|
|
16678
|
-
return void 0;
|
|
16679
|
-
}
|
|
16680
16788
|
function isGraphqlRead(args) {
|
|
16681
|
-
const method =
|
|
16789
|
+
const method = extractGhApiMethod(args);
|
|
16682
16790
|
if (method) return READ_METHODS.has(method.toUpperCase());
|
|
16683
16791
|
const queryBody = extractGraphqlQuery(args);
|
|
16684
16792
|
if (!queryBody) return false;
|
|
@@ -16896,51 +17004,6 @@ function matchesConfigDeny(command) {
|
|
|
16896
17004
|
);
|
|
16897
17005
|
}
|
|
16898
17006
|
|
|
16899
|
-
// src/commands/cliHook/findTruncatedReadDeny.ts
|
|
16900
|
-
var UNTRUNCATABLE_READS = ["assist backlog show", "assist backlog view"];
|
|
16901
|
-
var APPROVAL_GATED_COMMANDS = [
|
|
16902
|
-
"assist backlog propose",
|
|
16903
|
-
"assist backlog comment",
|
|
16904
|
-
"assist backlog update-plan",
|
|
16905
|
-
"assist backlog add-phase",
|
|
16906
|
-
"assist github issue create",
|
|
16907
|
-
"assist github issue edit",
|
|
16908
|
-
"assist github issue comment",
|
|
16909
|
-
"assist slack post",
|
|
16910
|
-
"assist prs raise",
|
|
16911
|
-
"assist prs edit",
|
|
16912
|
-
"assist prs comment",
|
|
16913
|
-
"assist prs reply",
|
|
16914
|
-
"assist prs wontfix",
|
|
16915
|
-
"assist miro extract"
|
|
16916
|
-
];
|
|
16917
|
-
var TRUNCATOR_BINARIES = ["head", "tail"];
|
|
16918
|
-
var PIPED_TRUNCATOR_RE = /\|\s*(?:\S*\/)?(?:head|tail)\b/;
|
|
16919
|
-
function matchPrefix(prefixes, part) {
|
|
16920
|
-
return prefixes.find(
|
|
16921
|
-
(prefix2) => part === prefix2 || part.startsWith(`${prefix2} `)
|
|
16922
|
-
);
|
|
16923
|
-
}
|
|
16924
|
-
function isTruncator(part) {
|
|
16925
|
-
const binary = part.split(/\s+/)[0]?.split("/").pop() ?? "";
|
|
16926
|
-
return TRUNCATOR_BINARIES.includes(binary) || PIPED_TRUNCATOR_RE.test(part);
|
|
16927
|
-
}
|
|
16928
|
-
function findTruncatedReadDeny(parts) {
|
|
16929
|
-
const read3 = parts.map((part) => matchPrefix(UNTRUNCATABLE_READS, part)).find(Boolean);
|
|
16930
|
-
const gated = parts.map((part) => matchPrefix(APPROVAL_GATED_COMMANDS, part)).find(Boolean);
|
|
16931
|
-
if (!read3 && !gated) return void 0;
|
|
16932
|
-
if (!parts.some(isTruncator)) return void 0;
|
|
16933
|
-
if (read3)
|
|
16934
|
-
return {
|
|
16935
|
-
permissionDecision: "deny",
|
|
16936
|
-
permissionDecisionReason: `Do not pipe '${read3}' through head or tail. Plan, Activity and Comments print at the end of the output, so a truncated read drops them and leaves you assuming the item has none. Run '${read3} <id>' bare and read all of it, or use a focused view: 'assist backlog comments <id>' for comments only.`
|
|
16937
|
-
};
|
|
16938
|
-
return {
|
|
16939
|
-
permissionDecision: "deny",
|
|
16940
|
-
permissionDecisionReason: `Do not pipe '${gated}' through head or tail. It gates on a preview the reviewer can reject with inline comments, and those comments print at the end of the output. Nothing persists them, so a truncated read discards the reviewer's feedback for good and they have to retype it. Run '${gated}' bare and read all of it.`
|
|
16941
|
-
};
|
|
16942
|
-
}
|
|
16943
|
-
|
|
16944
17007
|
// src/commands/cliHook/findSubcommandAdvice.ts
|
|
16945
17008
|
var SUBCOMMAND_READS = [
|
|
16946
17009
|
{
|
|
@@ -17014,7 +17077,7 @@ function resolvePermission(toolName, parts) {
|
|
|
17014
17077
|
function decideCommand(toolName, rawCommand) {
|
|
17015
17078
|
const result = splitCompound(rawCommand);
|
|
17016
17079
|
if (result.ok) return resolvePermission(toolName, result.parts);
|
|
17017
|
-
return findBuiltinDenyRaw(rawCommand) ?? findDeny(toolName, [rawCommand]);
|
|
17080
|
+
return findBuiltinDenyRaw(rawCommand) ?? findTruncatedReadDenyRaw(rawCommand) ?? findDeny(toolName, [rawCommand]);
|
|
17018
17081
|
}
|
|
17019
17082
|
|
|
17020
17083
|
// src/commands/cliHook/logDeniedToolCall.ts
|
|
@@ -17117,6 +17180,7 @@ function cliHookCheck(command, toolName = "Bash") {
|
|
|
17117
17180
|
const result = splitCompound(trimmed);
|
|
17118
17181
|
if (!result.ok) {
|
|
17119
17182
|
if (reportDecision(findBuiltinDenyRaw(trimmed))) return;
|
|
17183
|
+
if (reportDecision(findTruncatedReadDenyRaw(trimmed))) return;
|
|
17120
17184
|
if (reportDeny(toolName, [trimmed])) return;
|
|
17121
17185
|
console.log(`not approved (${result.error})`);
|
|
17122
17186
|
process.exitCode = 1;
|
|
@@ -22514,12 +22578,79 @@ function registerEditIssue(issueCommand) {
|
|
|
22514
22578
|
).action(editIssue);
|
|
22515
22579
|
}
|
|
22516
22580
|
|
|
22581
|
+
// src/commands/github/issue/editIssueComment.ts
|
|
22582
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
22583
|
+
var USAGE4 = "Usage: assist github issue edit-comment <comment-id> --body <body> [-R <owner>/<repo>]";
|
|
22584
|
+
async function editIssueComment(commentIdArg, options2) {
|
|
22585
|
+
const commentId = Number.parseInt(commentIdArg, 10);
|
|
22586
|
+
if (!Number.isInteger(commentId) || commentId <= 0 || !options2.body) {
|
|
22587
|
+
console.error(USAGE4);
|
|
22588
|
+
process.exit(1);
|
|
22589
|
+
}
|
|
22590
|
+
const { body } = options2;
|
|
22591
|
+
const { owner, repo } = resolveIssueRepoTarget(options2.repo);
|
|
22592
|
+
const target = `${owner}/${repo} comment ${commentId}`;
|
|
22593
|
+
validateProposedContent(
|
|
22594
|
+
{ subject: "Comment", context: "GitHub issues" },
|
|
22595
|
+
"",
|
|
22596
|
+
body
|
|
22597
|
+
);
|
|
22598
|
+
await reviewProposedIssueComment(`Amend ${target}`, body);
|
|
22599
|
+
const args = [
|
|
22600
|
+
"api",
|
|
22601
|
+
"-X",
|
|
22602
|
+
"PATCH",
|
|
22603
|
+
`repos/${owner}/${repo}/issues/comments/${commentId}`,
|
|
22604
|
+
"--input",
|
|
22605
|
+
"-"
|
|
22606
|
+
];
|
|
22607
|
+
let raw;
|
|
22608
|
+
try {
|
|
22609
|
+
raw = execFileSync11("gh", args, {
|
|
22610
|
+
input: JSON.stringify({ body }),
|
|
22611
|
+
encoding: "utf8"
|
|
22612
|
+
});
|
|
22613
|
+
} catch {
|
|
22614
|
+
process.exit(1);
|
|
22615
|
+
}
|
|
22616
|
+
console.log(`Comment updated: ${commentUrl(raw) ?? target}`);
|
|
22617
|
+
}
|
|
22618
|
+
function commentUrl(raw) {
|
|
22619
|
+
try {
|
|
22620
|
+
const parsed = JSON.parse(raw);
|
|
22621
|
+
return parsed.html_url;
|
|
22622
|
+
} catch {
|
|
22623
|
+
return void 0;
|
|
22624
|
+
}
|
|
22625
|
+
}
|
|
22626
|
+
|
|
22627
|
+
// src/commands/github/issue/registerEditIssueComment.ts
|
|
22628
|
+
function registerEditIssueComment(issueCommand) {
|
|
22629
|
+
issueCommand.command("edit-comment <comment-id>").description(
|
|
22630
|
+
"Replace the body of a posted GitHub issue comment (body of - reads it from stdin)"
|
|
22631
|
+
).option("--body <body>", "Replacement comment body (- reads it from stdin)").option(
|
|
22632
|
+
"-R, --repo <owner/repo>",
|
|
22633
|
+
"Target repository (defaults to the current repo)"
|
|
22634
|
+
).addHelpText(
|
|
22635
|
+
"after",
|
|
22636
|
+
"\nThe comment id is the numeric id from the comment's API url or the #issuecomment-<id> anchor on github.com, not the issue number.\nThe replacement is outward-facing: write it for the repo's readers, not the team. It is rejected if it references Claude or an assist backlog item.\nIn an assist web session the replacement is previewed for approve/reject first (with inline comments); the published comment is left alone until it is approved. The replacement is the whole body \u2014 what is there now is overwritten, so fetch it first if it should be built on."
|
|
22637
|
+
).action(
|
|
22638
|
+
async (commentId, options2) => {
|
|
22639
|
+
await editIssueComment(commentId, {
|
|
22640
|
+
...options2,
|
|
22641
|
+
body: options2.body ? await readBodyArgument(options2.body) : void 0
|
|
22642
|
+
});
|
|
22643
|
+
}
|
|
22644
|
+
);
|
|
22645
|
+
}
|
|
22646
|
+
|
|
22517
22647
|
// src/commands/registerGithubIssue.ts
|
|
22518
22648
|
function registerGithubIssue(githubCommand) {
|
|
22519
22649
|
const issueCommand = githubCommand.command("issue").description("GitHub issue utilities");
|
|
22520
22650
|
registerCreateIssue(issueCommand);
|
|
22521
22651
|
registerEditIssue(issueCommand);
|
|
22522
22652
|
registerCommentIssue(issueCommand);
|
|
22653
|
+
registerEditIssueComment(issueCommand);
|
|
22523
22654
|
registerFixStructure(issueCommand);
|
|
22524
22655
|
}
|
|
22525
22656
|
|
|
@@ -22948,7 +23079,7 @@ function registerRefineLaunch(program2, resumeFlag) {
|
|
|
22948
23079
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
22949
23080
|
|
|
22950
23081
|
// src/commands/review/checkoutPr.ts
|
|
22951
|
-
import { execFileSync as
|
|
23082
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
22952
23083
|
import chalk170 from "chalk";
|
|
22953
23084
|
|
|
22954
23085
|
// src/commands/sessions/daemon/daemonLog.ts
|
|
@@ -23508,10 +23639,10 @@ async function moveToPrCheckoutTree() {
|
|
|
23508
23639
|
}
|
|
23509
23640
|
|
|
23510
23641
|
// src/commands/review/prHeadBranch.ts
|
|
23511
|
-
import { execFileSync as
|
|
23642
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
23512
23643
|
function prHeadBranch(number) {
|
|
23513
23644
|
try {
|
|
23514
|
-
const out =
|
|
23645
|
+
const out = execFileSync12(
|
|
23515
23646
|
"gh",
|
|
23516
23647
|
["pr", "view", number, "--json", "headRefName", "-q", ".headRefName"],
|
|
23517
23648
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
|
@@ -23559,7 +23690,7 @@ async function checkoutPr(number) {
|
|
|
23559
23690
|
if (headRef && moveToExistingCheckout(number, headRef)) return;
|
|
23560
23691
|
await moveToPrCheckoutTree();
|
|
23561
23692
|
try {
|
|
23562
|
-
|
|
23693
|
+
execFileSync13("gh", ["pr", "checkout", number], { stdio: "inherit" });
|
|
23563
23694
|
} catch {
|
|
23564
23695
|
console.error(chalk170.red(`gh pr checkout ${number} failed; aborting.`));
|
|
23565
23696
|
process.exit(1);
|
|
@@ -25221,13 +25352,13 @@ async function comment2(path90, line, body, startLine) {
|
|
|
25221
25352
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
25222
25353
|
|
|
25223
25354
|
// src/commands/prs/applyEdit.ts
|
|
25224
|
-
import { execFileSync as
|
|
25355
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
25225
25356
|
function applyEdit(number, title, body) {
|
|
25226
25357
|
const args = ["pr", "edit", String(number)];
|
|
25227
25358
|
if (title) args.push("--title", title);
|
|
25228
25359
|
args.push("--body", body);
|
|
25229
25360
|
try {
|
|
25230
|
-
|
|
25361
|
+
execFileSync14("gh", args, { stdio: "inherit" });
|
|
25231
25362
|
} catch {
|
|
25232
25363
|
process.exit(1);
|
|
25233
25364
|
}
|
|
@@ -26023,7 +26154,7 @@ function buildValidatedBody(options2, usage) {
|
|
|
26023
26154
|
}
|
|
26024
26155
|
|
|
26025
26156
|
// src/commands/prs/placePr.ts
|
|
26026
|
-
import { execFileSync as
|
|
26157
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
26027
26158
|
|
|
26028
26159
|
// src/commands/prs/buildCreateArgs.ts
|
|
26029
26160
|
function buildEditArgs(number, title, body) {
|
|
@@ -26090,7 +26221,7 @@ async function recordPrActivity() {
|
|
|
26090
26221
|
// src/commands/prs/placePr.ts
|
|
26091
26222
|
function hasUpstream2() {
|
|
26092
26223
|
try {
|
|
26093
|
-
|
|
26224
|
+
execFileSync15(
|
|
26094
26225
|
"git",
|
|
26095
26226
|
["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
|
|
26096
26227
|
{ stdio: "pipe" }
|
|
@@ -26102,13 +26233,13 @@ function hasUpstream2() {
|
|
|
26102
26233
|
}
|
|
26103
26234
|
function ensureBranchPushed() {
|
|
26104
26235
|
const args = hasUpstream2() ? ["push"] : ["push", "--set-upstream", "origin", "HEAD"];
|
|
26105
|
-
|
|
26236
|
+
execFileSync15("git", args, { stdio: "inherit" });
|
|
26106
26237
|
}
|
|
26107
26238
|
async function placePr(prNumber, title, body, options2) {
|
|
26108
26239
|
const args = prNumber !== null ? buildEditArgs(prNumber, title, body) : buildCreateArgs(title, body, options2);
|
|
26109
26240
|
try {
|
|
26110
26241
|
if (prNumber === null && !options2.head) ensureBranchPushed();
|
|
26111
|
-
|
|
26242
|
+
execFileSync15("gh", args, { stdio: "inherit" });
|
|
26112
26243
|
} catch {
|
|
26113
26244
|
process.exit(1);
|
|
26114
26245
|
}
|
|
@@ -26271,9 +26402,9 @@ function resolveDraftState(options2, command) {
|
|
|
26271
26402
|
}
|
|
26272
26403
|
|
|
26273
26404
|
// src/commands/prs/raise.ts
|
|
26274
|
-
var
|
|
26405
|
+
var USAGE5 = "Usage: assist prs raise --title <title> --what <what> --why <why> [--how <how>] [--resolves <key>] [--force]";
|
|
26275
26406
|
async function raise(options2, command) {
|
|
26276
|
-
const { title, body } = buildValidatedBody(options2,
|
|
26407
|
+
const { title, body } = buildValidatedBody(options2, USAGE5);
|
|
26277
26408
|
const resolved = { ...options2, draft: resolveDraftState(options2, command) };
|
|
26278
26409
|
const existing = findCurrentPrNumber();
|
|
26279
26410
|
const sessionId = process.env.ASSIST_SESSION_ID;
|
|
@@ -31265,7 +31396,7 @@ function slackWorkingFile(channel) {
|
|
|
31265
31396
|
}
|
|
31266
31397
|
|
|
31267
31398
|
// src/commands/slack/postSlackMessage.ts
|
|
31268
|
-
var
|
|
31399
|
+
var USAGE6 = "Usage: assist slack post [channel] --body <body|->";
|
|
31269
31400
|
async function postSlackMessage(channelArg, options2) {
|
|
31270
31401
|
const channel = (channelArg ?? loadConfig().slack?.channel ?? "").trim();
|
|
31271
31402
|
const body = options2.body?.trim();
|
|
@@ -31273,11 +31404,11 @@ async function postSlackMessage(channelArg, options2) {
|
|
|
31273
31404
|
console.error(
|
|
31274
31405
|
'No channel given and slack.channel is not set. Pass one, or set a default with: assist config set slack.channel "#general"'
|
|
31275
31406
|
);
|
|
31276
|
-
console.error(
|
|
31407
|
+
console.error(USAGE6);
|
|
31277
31408
|
process.exit(1);
|
|
31278
31409
|
}
|
|
31279
31410
|
if (!body) {
|
|
31280
|
-
console.error(
|
|
31411
|
+
console.error(USAGE6);
|
|
31281
31412
|
process.exit(1);
|
|
31282
31413
|
}
|
|
31283
31414
|
const threadTs = options2.thread;
|
|
@@ -31874,7 +32005,7 @@ function syncPiHooks(sourceDir) {
|
|
|
31874
32005
|
}
|
|
31875
32006
|
|
|
31876
32007
|
// src/commands/sync/syncPi.ts
|
|
31877
|
-
function
|
|
32008
|
+
function unquote2(value) {
|
|
31878
32009
|
return value.trim().replace(/^["']|["']$/g, "");
|
|
31879
32010
|
}
|
|
31880
32011
|
function commandToPrompt(name, content) {
|
|
@@ -31882,9 +32013,9 @@ function commandToPrompt(name, content) {
|
|
|
31882
32013
|
const frontmatter = match ? match[1] : "";
|
|
31883
32014
|
const body = match ? content.slice(match[0].length) : content;
|
|
31884
32015
|
const descriptionMatch = frontmatter.match(/^description:\s*(.*)$/m);
|
|
31885
|
-
const description = descriptionMatch ?
|
|
32016
|
+
const description = descriptionMatch ? unquote2(descriptionMatch[1]) : name;
|
|
31886
32017
|
const argsMatch = frontmatter.match(/^allowed_args:\s*(.*)$/m);
|
|
31887
|
-
const argumentHint = argsMatch ?
|
|
32018
|
+
const argumentHint = argsMatch ? unquote2(argsMatch[1]) : void 0;
|
|
31888
32019
|
const header = [
|
|
31889
32020
|
"---",
|
|
31890
32021
|
`description: ${description}`,
|
|
@@ -32738,9 +32869,9 @@ function registerVoice(program2) {
|
|
|
32738
32869
|
}
|
|
32739
32870
|
|
|
32740
32871
|
// src/commands/watch/resolveUpstream.ts
|
|
32741
|
-
import { execFileSync as
|
|
32872
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
32742
32873
|
function runGit3(args, cwd) {
|
|
32743
|
-
return
|
|
32874
|
+
return execFileSync16("git", args, {
|
|
32744
32875
|
encoding: "utf8",
|
|
32745
32876
|
stdio: ["pipe", "pipe", "pipe"],
|
|
32746
32877
|
cwd
|
|
@@ -33146,13 +33277,13 @@ import { spawn as spawn9 } from "child_process";
|
|
|
33146
33277
|
import { existsSync as existsSync80 } from "fs";
|
|
33147
33278
|
|
|
33148
33279
|
// src/commands/run/resolveCommand.ts
|
|
33149
|
-
import { execFileSync as
|
|
33280
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
33150
33281
|
import { existsSync as existsSync79 } from "fs";
|
|
33151
33282
|
import { dirname as dirname37, join as join92, resolve as resolve20 } from "path";
|
|
33152
33283
|
function resolveCommand2(command) {
|
|
33153
33284
|
if (process.platform !== "win32" || command !== "bash") return command;
|
|
33154
33285
|
try {
|
|
33155
|
-
const gitPath =
|
|
33286
|
+
const gitPath = execFileSync17("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
|
|
33156
33287
|
const gitRoot = resolve20(dirname37(gitPath), "..");
|
|
33157
33288
|
const gitBash = join92(gitRoot, "bin", "bash.exe");
|
|
33158
33289
|
if (existsSync79(gitBash)) return gitBash;
|
|
@@ -33274,11 +33405,11 @@ async function reportSyncOrExit() {
|
|
|
33274
33405
|
}
|
|
33275
33406
|
|
|
33276
33407
|
// src/commands/watch/fetchQuietly.ts
|
|
33277
|
-
import { execFileSync as
|
|
33408
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
33278
33409
|
var MIN_FETCH_TIMEOUT_MS = 6e4;
|
|
33279
33410
|
function fetchQuietly(cwd, intervalMs) {
|
|
33280
33411
|
try {
|
|
33281
|
-
|
|
33412
|
+
execFileSync18("git", ["fetch", "--quiet"], {
|
|
33282
33413
|
stdio: ["pipe", "pipe", "pipe"],
|
|
33283
33414
|
cwd,
|
|
33284
33415
|
timeout: Math.max(intervalMs, MIN_FETCH_TIMEOUT_MS)
|
|
@@ -33580,7 +33711,7 @@ async function auth() {
|
|
|
33580
33711
|
}
|
|
33581
33712
|
|
|
33582
33713
|
// src/commands/roam/postRoamActivity.ts
|
|
33583
|
-
import { execFileSync as
|
|
33714
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
33584
33715
|
import { readdirSync as readdirSync21, readFileSync as readFileSync60, statSync as statSync12 } from "fs";
|
|
33585
33716
|
import { join as join93 } from "path";
|
|
33586
33717
|
function findPortFile(roamDir) {
|
|
@@ -33619,7 +33750,7 @@ function postRoamActivity(app, event) {
|
|
|
33619
33750
|
const pid = PID_BY_APP[app] ?? 99999;
|
|
33620
33751
|
const url = `http://127.0.0.1:${port}/api/v1/activity/${app}/${event}?pid=${pid}`;
|
|
33621
33752
|
try {
|
|
33622
|
-
|
|
33753
|
+
execFileSync19("curl", ["-sf", "--max-time", "0.2", "-X", "POST", url], {
|
|
33623
33754
|
stdio: "ignore"
|
|
33624
33755
|
});
|
|
33625
33756
|
} catch {
|
|
@@ -34097,11 +34228,11 @@ function screenshot(processName) {
|
|
|
34097
34228
|
}
|
|
34098
34229
|
|
|
34099
34230
|
// src/commands/sessions/daemon/listDaemonPids.ts
|
|
34100
|
-
import { execFileSync as
|
|
34231
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
34101
34232
|
function listDaemonPids() {
|
|
34102
34233
|
if (process.platform === "win32") return [];
|
|
34103
34234
|
try {
|
|
34104
|
-
const out =
|
|
34235
|
+
const out = execFileSync20("ps", ["-eo", "pid=,args="], {
|
|
34105
34236
|
encoding: "utf8"
|
|
34106
34237
|
});
|
|
34107
34238
|
return out.split("\n").filter((line) => line.includes("assist") && / daemon run\b/.test(line)).map((line) => Number.parseInt(line.trim(), 10)).filter((pid) => Number.isInteger(pid));
|
|
@@ -39728,7 +39859,7 @@ function cleanupOwnedFiles() {
|
|
|
39728
39859
|
import * as net3 from "net";
|
|
39729
39860
|
|
|
39730
39861
|
// src/commands/sessions/daemon/findPortHolderPid.ts
|
|
39731
|
-
import { execFileSync as
|
|
39862
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
39732
39863
|
var PROBE_TIMEOUT_MS = 3e3;
|
|
39733
39864
|
function findPortHolderPid(port) {
|
|
39734
39865
|
try {
|
|
@@ -39738,7 +39869,7 @@ function findPortHolderPid(port) {
|
|
|
39738
39869
|
}
|
|
39739
39870
|
}
|
|
39740
39871
|
function probe(command, args) {
|
|
39741
|
-
return
|
|
39872
|
+
return execFileSync21(command, args, {
|
|
39742
39873
|
encoding: "utf8",
|
|
39743
39874
|
timeout: PROBE_TIMEOUT_MS,
|
|
39744
39875
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -39977,7 +40108,7 @@ function summaryPathFor(jsonlPath2) {
|
|
|
39977
40108
|
}
|
|
39978
40109
|
|
|
39979
40110
|
// src/commands/sessions/summarise/summariseSession.ts
|
|
39980
|
-
import { execFileSync as
|
|
40111
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
39981
40112
|
function summariseSession(jsonlPath2) {
|
|
39982
40113
|
const firstMessage = extractFirstUserMessage(jsonlPath2);
|
|
39983
40114
|
const backlogIds = scanSessionBacklogRefs(jsonlPath2);
|
|
@@ -39986,7 +40117,7 @@ function summariseSession(jsonlPath2) {
|
|
|
39986
40117
|
}
|
|
39987
40118
|
const prompt = buildPrompt6(firstMessage, backlogIds);
|
|
39988
40119
|
try {
|
|
39989
|
-
const output =
|
|
40120
|
+
const output = execFileSync22("claude", ["-p", "--model", "haiku", prompt], {
|
|
39990
40121
|
encoding: "utf8",
|
|
39991
40122
|
timeout: 3e4,
|
|
39992
40123
|
stdio: ["ignore", "pipe", "ignore"]
|