@wrongstack/tools 0.305.1 → 0.306.2
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/_shell-pick.d.ts +4 -5
- package/dist/_util.d.ts +22 -5
- package/dist/audit.d.ts +0 -1
- package/dist/audit.js +135 -46
- package/dist/bash.js +61 -37
- package/dist/browser/index.js +29 -9
- package/dist/browser/types.d.ts +7 -1
- package/dist/builtin.js +1294 -726
- package/dist/codebase-index/codebase-search-tool.d.ts +5 -0
- package/dist/codebase-index/index.js +223 -152
- package/dist/codebase-index/project-server.js +10 -11
- package/dist/diff.d.ts +5 -0
- package/dist/diff.js +78 -12
- package/dist/document.js +18 -6
- package/dist/edit.js +69 -16
- package/dist/exec.js +44 -22
- package/dist/fetch.js +13 -1
- package/dist/format.d.ts +4 -2
- package/dist/format.js +81 -31
- package/dist/glob.js +12 -4
- package/dist/grep.d.ts +2 -0
- package/dist/grep.js +15 -4
- package/dist/index.js +1359 -762
- package/dist/install.js +96 -37
- package/dist/kanban-tool-types.d.ts +6 -1
- package/dist/kanban.js +60 -0
- package/dist/languages/index.js +28 -13
- package/dist/lint.js +28 -13
- package/dist/logs.d.ts +0 -1
- package/dist/logs.js +44 -13
- package/dist/memory.d.ts +8 -0
- package/dist/memory.js +23 -3
- package/dist/mode.d.ts +1 -1
- package/dist/mode.js +3 -0
- package/dist/next-steps.d.ts +2 -3
- package/dist/next-steps.js +3 -3
- package/dist/outdated.d.ts +0 -3
- package/dist/outdated.js +89 -48
- package/dist/pack.js +1294 -726
- package/dist/plan.js +91 -3
- package/dist/process-registry.d.ts +8 -2
- package/dist/process-registry.js +28 -13
- package/dist/ps-slash.js +22 -12
- package/dist/read.js +10 -3
- package/dist/replace.d.ts +4 -0
- package/dist/replace.js +104 -7
- package/dist/search.d.ts +6 -0
- package/dist/search.js +47 -26
- package/dist/session-kanban.js +3 -1
- package/dist/skill.d.ts +6 -0
- package/dist/skill.js +9 -10
- package/dist/task.js +81 -2
- package/dist/test.js +28 -13
- package/dist/todo.js +79 -2
- package/dist/tool-icons.js +4 -2
- package/dist/tool-summary.d.ts +1 -1
- package/dist/tool-summary.js +76 -1
- package/dist/tool-tier.js +1294 -726
- package/dist/tree.js +9 -10
- package/dist/typecheck.d.ts +0 -2
- package/dist/typecheck.js +98 -31
- package/dist/write.js +58 -10
- package/package.json +4 -4
package/dist/install.js
CHANGED
|
@@ -371,8 +371,11 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
371
371
|
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
372
372
|
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
373
373
|
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
374
|
+
// The value must be token-like (>= 8 chars) so ordinary combined flags such
|
|
375
|
+
// as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
|
|
376
|
+
// redacted, not just the first.
|
|
374
377
|
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
375
|
-
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]
|
|
378
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
|
|
376
379
|
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
377
380
|
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
378
381
|
// redaction function. Synced with core copy.
|
|
@@ -380,8 +383,9 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
380
383
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
381
384
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
382
385
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
383
|
-
// when preceded by a flag name (e.g. --github-token=EyJ...).
|
|
384
|
-
|
|
386
|
+
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
387
|
+
// every such flag in the command line is redacted, not just the first.
|
|
388
|
+
/--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
|
|
385
389
|
];
|
|
386
390
|
function redactCommand(cmd) {
|
|
387
391
|
let result = cmd;
|
|
@@ -470,11 +474,15 @@ var ProcessRegistryImpl = class {
|
|
|
470
474
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
|
|
471
475
|
}
|
|
472
476
|
_canSignalProcessGroup(p) {
|
|
473
|
-
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
477
|
+
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && p.child !== null && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
474
478
|
}
|
|
475
479
|
_killChildDirect(p, signal) {
|
|
476
480
|
try {
|
|
477
|
-
p.child
|
|
481
|
+
if (p.child) {
|
|
482
|
+
p.child.kill(signal);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (this._isSafeSignalPid(p.pid)) process.kill(p.pid, signal);
|
|
478
486
|
} catch {
|
|
479
487
|
}
|
|
480
488
|
}
|
|
@@ -672,15 +680,15 @@ var ProcessRegistryImpl = class {
|
|
|
672
680
|
this._pruneStale(pid);
|
|
673
681
|
const p = this.processes.get(pid);
|
|
674
682
|
if (!p) return false;
|
|
675
|
-
if (p.killed) return true;
|
|
683
|
+
if (p.killed && opts.force !== true) return true;
|
|
676
684
|
if (p.protected && opts.includeProtected !== true) return false;
|
|
677
685
|
if (opts.preserveBackground && p.background) return false;
|
|
678
686
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
679
687
|
const isWin2 = os.platform() === "win32";
|
|
680
688
|
if (isWin2) {
|
|
681
|
-
const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
|
|
689
|
+
const liveRealChild = p.child === null || p.child.exitCode === null && typeof p.child.pid === "number";
|
|
682
690
|
const directFallback = () => {
|
|
683
|
-
if (p.child.exitCode === null) {
|
|
691
|
+
if (p.child && p.child.exitCode === null) {
|
|
684
692
|
try {
|
|
685
693
|
p.child.kill("SIGKILL");
|
|
686
694
|
} catch {
|
|
@@ -692,10 +700,7 @@ var ProcessRegistryImpl = class {
|
|
|
692
700
|
onSettled: directFallback
|
|
693
701
|
})) {
|
|
694
702
|
} else {
|
|
695
|
-
|
|
696
|
-
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
697
|
-
} catch {
|
|
698
|
-
}
|
|
703
|
+
this._killChildDirect(p, force ? "SIGKILL" : "SIGTERM");
|
|
699
704
|
}
|
|
700
705
|
p.killed = true;
|
|
701
706
|
return true;
|
|
@@ -706,7 +711,7 @@ var ProcessRegistryImpl = class {
|
|
|
706
711
|
} else {
|
|
707
712
|
this._killPosix(p, "SIGTERM");
|
|
708
713
|
const timer = setTimeout(() => {
|
|
709
|
-
if (this.processes.has(pid) && !p.child
|
|
714
|
+
if (this.processes.has(pid) && !p.child?.killed) {
|
|
710
715
|
this._killPosix(p, "SIGKILL");
|
|
711
716
|
}
|
|
712
717
|
}, graceMs);
|
|
@@ -761,6 +766,16 @@ var ProcessRegistryImpl = class {
|
|
|
761
766
|
* before reusing a PID, but we want to clean up before that becomes a risk.
|
|
762
767
|
*/
|
|
763
768
|
_isStaleEntry(entry) {
|
|
769
|
+
if (entry.child === null) {
|
|
770
|
+
if (Date.now() - entry.startedAt <= 6e4) return false;
|
|
771
|
+
if (os.platform() === "win32") return false;
|
|
772
|
+
try {
|
|
773
|
+
process.kill(entry.pid, 0);
|
|
774
|
+
return false;
|
|
775
|
+
} catch (err) {
|
|
776
|
+
return err.code !== "EPERM";
|
|
777
|
+
}
|
|
778
|
+
}
|
|
764
779
|
return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
|
|
765
780
|
}
|
|
766
781
|
/**
|
|
@@ -1035,19 +1050,48 @@ function utf8Prefix(text, maxBytes) {
|
|
|
1035
1050
|
// src/_util.ts
|
|
1036
1051
|
import * as path3 from "node:path";
|
|
1037
1052
|
import * as Core from "@wrongstack/core/utils";
|
|
1038
|
-
async function detectPackageManager(cwd) {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1053
|
+
async function detectPackageManager(cwd, stopAt) {
|
|
1054
|
+
let dir = path3.resolve(cwd);
|
|
1055
|
+
const stop = stopAt ? path3.resolve(stopAt) : dir;
|
|
1056
|
+
for (; ; ) {
|
|
1057
|
+
const found = await detectPackageManagerInDir(dir);
|
|
1058
|
+
if (found) return found;
|
|
1059
|
+
if (dir === stop) break;
|
|
1060
|
+
const parent = path3.dirname(dir);
|
|
1061
|
+
const relParent = path3.relative(stop, parent);
|
|
1062
|
+
if (parent === dir || relParent.startsWith("..") || path3.isAbsolute(relParent)) break;
|
|
1063
|
+
dir = parent;
|
|
1044
1064
|
}
|
|
1065
|
+
return "npm";
|
|
1066
|
+
}
|
|
1067
|
+
async function detectPackageManagerInDir(dir) {
|
|
1068
|
+
const fs6 = await import("node:fs/promises");
|
|
1045
1069
|
try {
|
|
1046
|
-
await
|
|
1047
|
-
|
|
1070
|
+
const raw = await fs6.readFile(path3.join(dir, "package.json"), "utf8");
|
|
1071
|
+
const declared = JSON.parse(raw).packageManager;
|
|
1072
|
+
if (typeof declared === "string") {
|
|
1073
|
+
const name = declared.split("@")[0] ?? "";
|
|
1074
|
+
if (name === "pnpm" || name === "yarn") return name;
|
|
1075
|
+
if (name === "npm" || name === "bun") return "npm";
|
|
1076
|
+
}
|
|
1048
1077
|
} catch {
|
|
1049
1078
|
}
|
|
1050
|
-
|
|
1079
|
+
const lockfiles = [
|
|
1080
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
1081
|
+
["yarn.lock", "yarn"],
|
|
1082
|
+
["bun.lockb", "npm"],
|
|
1083
|
+
["bun.lock", "npm"],
|
|
1084
|
+
["package-lock.json", "npm"],
|
|
1085
|
+
["npm-shrinkwrap.json", "npm"]
|
|
1086
|
+
];
|
|
1087
|
+
for (const [file, manager] of lockfiles) {
|
|
1088
|
+
try {
|
|
1089
|
+
await fs6.stat(`${dir}/${file}`);
|
|
1090
|
+
return manager;
|
|
1091
|
+
} catch {
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return null;
|
|
1051
1095
|
}
|
|
1052
1096
|
function resolvePath(input, ctx) {
|
|
1053
1097
|
return path3.isAbsolute(input) ? path3.normalize(input) : path3.resolve(ctx.workingDir ?? ctx.cwd, input);
|
|
@@ -3891,26 +3935,14 @@ var installTool = {
|
|
|
3891
3935
|
return;
|
|
3892
3936
|
}
|
|
3893
3937
|
}
|
|
3894
|
-
const pkgManager = await detectPackageManager(cwd);
|
|
3938
|
+
const pkgManager = await detectPackageManager(cwd, ctx.projectRoot);
|
|
3895
3939
|
yield { type: "log", text: `Resolving with ${pkgManager}\u2026`, data: { phase: "resolve" } };
|
|
3896
|
-
const save = input.save === "dev" ? "-D" : input.save === "optional" ? "-O" : "";
|
|
3897
3940
|
const globalFlag = input.global ? ["-g"] : [];
|
|
3898
3941
|
const ignoreScripts = input.lifecycleScripts !== true;
|
|
3899
|
-
const args = [];
|
|
3900
|
-
if (input.dry_run) args.push("--dry-run");
|
|
3901
|
-
if (ignoreScripts) args.push("--ignore-scripts");
|
|
3902
|
-
if (pkgManager === "pnpm") {
|
|
3903
|
-
if (save) args.push(save);
|
|
3904
|
-
args.push("add", ...globalFlag);
|
|
3905
|
-
} else if (pkgManager === "yarn") {
|
|
3906
|
-
args.push("add", ...globalFlag);
|
|
3907
|
-
} else {
|
|
3908
|
-
args.push("install", ...globalFlag);
|
|
3909
|
-
}
|
|
3910
3942
|
const pkgList = input.packages ? (Array.isArray(input.packages) ? input.packages : input.packages.split(",")).map(
|
|
3911
3943
|
(p) => p.trim()
|
|
3912
3944
|
) : [];
|
|
3913
|
-
const PKG_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]
|
|
3945
|
+
const PKG_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9^~><=*.+-]+)?$/i;
|
|
3914
3946
|
for (const pkg of pkgList) {
|
|
3915
3947
|
if (!PKG_NAME_RE.test(pkg) || pkg.startsWith("-") || pkg.length > 200) {
|
|
3916
3948
|
yield {
|
|
@@ -3926,7 +3958,34 @@ var installTool = {
|
|
|
3926
3958
|
return;
|
|
3927
3959
|
}
|
|
3928
3960
|
}
|
|
3929
|
-
|
|
3961
|
+
const hasPkgs = pkgList.length > 0;
|
|
3962
|
+
const args = [];
|
|
3963
|
+
if (input.dry_run) args.push("--dry-run");
|
|
3964
|
+
if (ignoreScripts) args.push("--ignore-scripts");
|
|
3965
|
+
if (pkgManager === "pnpm") {
|
|
3966
|
+
if (hasPkgs) {
|
|
3967
|
+
if (input.save === "dev") args.push("-D");
|
|
3968
|
+
else if (input.save === "optional") args.push("-O");
|
|
3969
|
+
args.push("add", ...globalFlag);
|
|
3970
|
+
} else {
|
|
3971
|
+
args.push("install", ...globalFlag);
|
|
3972
|
+
}
|
|
3973
|
+
} else if (pkgManager === "yarn") {
|
|
3974
|
+
if (hasPkgs) {
|
|
3975
|
+
args.push("add", ...globalFlag);
|
|
3976
|
+
if (input.save === "dev") args.push("--dev");
|
|
3977
|
+
else if (input.save === "optional") args.push("--optional");
|
|
3978
|
+
} else {
|
|
3979
|
+
args.push("install", ...globalFlag);
|
|
3980
|
+
}
|
|
3981
|
+
} else {
|
|
3982
|
+
args.push("install", ...globalFlag);
|
|
3983
|
+
if (hasPkgs) {
|
|
3984
|
+
if (input.save === "dev") args.push("--save-dev");
|
|
3985
|
+
else if (input.save === "optional") args.push("--save-optional");
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
if (hasPkgs) args.push(...pkgList);
|
|
3930
3989
|
yield {
|
|
3931
3990
|
type: "log",
|
|
3932
3991
|
text: `Fetching ${pkgList.length || "all"} packages\u2026`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SerializedTaskGraph } from '@wrongstack/core/types';
|
|
2
2
|
import type { AssignKanbanTaskInput, KanbanAgentRunStatus, KanbanBoard, KanbanBoardSummary, KanbanCompletionGateEnforcement, KanbanContractEdgeType, KanbanContractEnforcement, KanbanContractGraph, KanbanContractGraphEnforcement, KanbanContractGraphEvaluation, KanbanContractNodeKind, KanbanContractNodeState, KanbanDecompositionSubtask, KanbanEvent, KanbanLifecycleStage, KanbanOrchestrationSnapshot, KanbanQueueHealth, KanbanSearchResult, KanbanTask, KanbanTaskPriority, KanbanTaskStatus, KanbanTaskType, KanbanVerificationReport, KanbanWorkbenchSnapshot } from '@wrongstack/kanban';
|
|
3
|
-
export type KanbanAction = 'list_boards' | 'get_board' | 'create_board' | 'duplicate_board' | 'update_board' | 'adopt_managed_lifecycle' | 'release_managed_lifecycle' | 'delete_board' | 'generate_board' | 'export_markdown' | 'export_task_graph' | 'sync_task_graph' | 'create_from_graph' | 'import_session_tasks' | 'search_tasks' | 'ready_tasks' | 'snapshot' | 'workbench' | '
|
|
3
|
+
export type KanbanAction = 'list_boards' | 'get_board' | 'create_board' | 'duplicate_board' | 'update_board' | 'adopt_managed_lifecycle' | 'release_managed_lifecycle' | 'delete_board' | 'generate_board' | 'export_markdown' | 'export_task_graph' | 'sync_task_graph' | 'create_from_graph' | 'import_session_tasks' | 'search_tasks' | 'ready_tasks' | 'snapshot' | 'workbench' | 'add_task' | 'split_task' | 'merge_tasks' | 'copy_task' | 'transfer_task' | 'get_task' | 'start_task' | 'update_task' | 'transition_task' | 'repair_managed_projection' | 'move_task' | 'delete_task' | 'set_chain' | 'get_chain' | 'claim_task' | 'release_task' | 'assign_task' | 'mark_assignment' | 'heartbeat_assignment' | 'recover_stale' | 'events' | 'queue_health' | 'add_dependency' | 'add_goal_metric' | 'update_goal_metric' | 'add_check' | 'update_check' | 'remove_check' | 'add_note' | 'add_link' | 'verify_completion' | 'split_atomic' | 'assess_atomicity' | 'propose_decomposition' | 'get_contract_graph' | 'configure_contract_graph' | 'upsert_contract_node' | 'remove_contract_node' | 'add_contract_edge' | 'remove_contract_edge';
|
|
4
4
|
export interface KanbanToolInput extends Omit<AssignKanbanTaskInput, 'status'> {
|
|
5
5
|
action: KanbanAction;
|
|
6
6
|
boardId?: string | undefined;
|
|
@@ -101,6 +101,11 @@ export interface KanbanToolInput extends Omit<AssignKanbanTaskInput, 'status'> {
|
|
|
101
101
|
lifecycleStage?: KanbanLifecycleStage | undefined;
|
|
102
102
|
transitionAction?: string | undefined;
|
|
103
103
|
transitionComment?: string | undefined;
|
|
104
|
+
/** `transition_task`: flip one or more manual criteria to `passed` before the gate fires. */
|
|
105
|
+
tickChecks?: {
|
|
106
|
+
checkId: string;
|
|
107
|
+
checkStatus: 'passed' | 'failed' | 'skipped';
|
|
108
|
+
}[] | undefined;
|
|
104
109
|
attachmentUrl?: string | undefined;
|
|
105
110
|
attachmentTitle?: string | undefined;
|
|
106
111
|
attachmentType?: 'issue' | 'pr' | 'doc' | 'commit' | 'design' | 'file' | 'url' | 'other' | undefined;
|
package/dist/kanban.js
CHANGED
|
@@ -860,6 +860,18 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
860
860
|
},
|
|
861
861
|
transitionAction: { type: "string" },
|
|
862
862
|
transitionComment: { type: "string" },
|
|
863
|
+
tickChecks: {
|
|
864
|
+
type: "array",
|
|
865
|
+
items: {
|
|
866
|
+
type: "object",
|
|
867
|
+
properties: {
|
|
868
|
+
checkId: { type: "string" },
|
|
869
|
+
checkStatus: { type: "string", enum: ["passed", "failed", "skipped"] }
|
|
870
|
+
},
|
|
871
|
+
required: ["checkId", "checkStatus"]
|
|
872
|
+
},
|
|
873
|
+
description: "`transition_task` (to=done only): flip one or more manual criteria to `passed` before the gate fires. Read ids from kanban get_task. Non-manual criteria are refused."
|
|
874
|
+
},
|
|
863
875
|
attachmentUrl: { type: "string" },
|
|
864
876
|
attachmentTitle: { type: "string" },
|
|
865
877
|
attachmentType: {
|
|
@@ -1115,6 +1127,9 @@ var kanbanTool = {
|
|
|
1115
1127
|
description: KANBAN_TOOL_DESCRIPTION,
|
|
1116
1128
|
usageHint: KANBAN_TOOL_USAGE_HINT,
|
|
1117
1129
|
permission: "confirm",
|
|
1130
|
+
// WS-046: gives permission decisions something to key on.
|
|
1131
|
+
// The action performed; kanban has no single file or path subject.
|
|
1132
|
+
subjectKey: "action",
|
|
1118
1133
|
mutating: true,
|
|
1119
1134
|
capabilities: ["fs.write"],
|
|
1120
1135
|
icon: "task",
|
|
@@ -1556,6 +1571,7 @@ var kanbanTool = {
|
|
|
1556
1571
|
actor: input.author,
|
|
1557
1572
|
comment: input.transitionComment,
|
|
1558
1573
|
...input.transitionAction !== void 0 ? { action: input.transitionAction } : {},
|
|
1574
|
+
...input.tickChecks !== void 0 ? { tickChecks: input.tickChecks } : {},
|
|
1559
1575
|
...input.attachmentUrl !== void 0 ? {
|
|
1560
1576
|
attachment: {
|
|
1561
1577
|
url: input.attachmentUrl,
|
|
@@ -1948,8 +1964,52 @@ var kanbanTool = {
|
|
|
1948
1964
|
} catch (err) {
|
|
1949
1965
|
return fail(stripLifecycleIssues(err instanceof Error ? err.message : String(err)));
|
|
1950
1966
|
}
|
|
1967
|
+
},
|
|
1968
|
+
serialize(output, input) {
|
|
1969
|
+
return serializeKanbanOutput(output, input);
|
|
1951
1970
|
}
|
|
1952
1971
|
};
|
|
1972
|
+
var KANBAN_BOARD_TRANSCRIPT_BYTE_CAP = 16384;
|
|
1973
|
+
var KANBAN_FULL_BOARD_ACTIONS = /* @__PURE__ */ new Set([
|
|
1974
|
+
"get_board",
|
|
1975
|
+
"export_markdown",
|
|
1976
|
+
"export_task_graph"
|
|
1977
|
+
]);
|
|
1978
|
+
function serializeKanbanOutput(output, input) {
|
|
1979
|
+
const action = input && typeof input === "object" ? input.action : void 0;
|
|
1980
|
+
const board = output.board;
|
|
1981
|
+
if (board) {
|
|
1982
|
+
const keepFull = typeof action === "string" && KANBAN_FULL_BOARD_ACTIONS.has(action);
|
|
1983
|
+
let boardBytes = 0;
|
|
1984
|
+
if (!keepFull) {
|
|
1985
|
+
try {
|
|
1986
|
+
boardBytes = Buffer.byteLength(JSON.stringify(board), "utf8");
|
|
1987
|
+
} catch {
|
|
1988
|
+
boardBytes = 0;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
if (!keepFull && boardBytes > KANBAN_BOARD_TRANSCRIPT_BYTE_CAP) {
|
|
1992
|
+
const columns = {};
|
|
1993
|
+
for (const column of board.columns) {
|
|
1994
|
+
columns[column.title || column.id] = board.tasks.filter(
|
|
1995
|
+
(task) => task.columnId === column.id
|
|
1996
|
+
).length;
|
|
1997
|
+
}
|
|
1998
|
+
const compact = {
|
|
1999
|
+
...output,
|
|
2000
|
+
board: {
|
|
2001
|
+
id: board.id,
|
|
2002
|
+
title: board.title,
|
|
2003
|
+
columns,
|
|
2004
|
+
totalTasks: board.tasks.length,
|
|
2005
|
+
note: `Full board (${boardBytes} bytes) omitted from the transcript; use get_board to load it.`
|
|
2006
|
+
}
|
|
2007
|
+
};
|
|
2008
|
+
return JSON.stringify(compact, null, 2);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
return JSON.stringify(output, null, 2);
|
|
2012
|
+
}
|
|
1953
2013
|
export {
|
|
1954
2014
|
kanbanTool
|
|
1955
2015
|
};
|
package/dist/languages/index.js
CHANGED
|
@@ -2492,8 +2492,11 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
2492
2492
|
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
2493
2493
|
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
2494
2494
|
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
2495
|
+
// The value must be token-like (>= 8 chars) so ordinary combined flags such
|
|
2496
|
+
// as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
|
|
2497
|
+
// redacted, not just the first.
|
|
2495
2498
|
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
2496
|
-
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]
|
|
2499
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
|
|
2497
2500
|
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
2498
2501
|
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
2499
2502
|
// redaction function. Synced with core copy.
|
|
@@ -2501,8 +2504,9 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
2501
2504
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
2502
2505
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
2503
2506
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
2504
|
-
// when preceded by a flag name (e.g. --github-token=EyJ...).
|
|
2505
|
-
|
|
2507
|
+
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
2508
|
+
// every such flag in the command line is redacted, not just the first.
|
|
2509
|
+
/--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
|
|
2506
2510
|
];
|
|
2507
2511
|
function redactCommand(cmd) {
|
|
2508
2512
|
let result = cmd;
|
|
@@ -2591,11 +2595,15 @@ var ProcessRegistryImpl = class {
|
|
|
2591
2595
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
|
|
2592
2596
|
}
|
|
2593
2597
|
_canSignalProcessGroup(p) {
|
|
2594
|
-
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
2598
|
+
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && p.child !== null && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
2595
2599
|
}
|
|
2596
2600
|
_killChildDirect(p, signal) {
|
|
2597
2601
|
try {
|
|
2598
|
-
p.child
|
|
2602
|
+
if (p.child) {
|
|
2603
|
+
p.child.kill(signal);
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
if (this._isSafeSignalPid(p.pid)) process.kill(p.pid, signal);
|
|
2599
2607
|
} catch {
|
|
2600
2608
|
}
|
|
2601
2609
|
}
|
|
@@ -2793,15 +2801,15 @@ var ProcessRegistryImpl = class {
|
|
|
2793
2801
|
this._pruneStale(pid);
|
|
2794
2802
|
const p = this.processes.get(pid);
|
|
2795
2803
|
if (!p) return false;
|
|
2796
|
-
if (p.killed) return true;
|
|
2804
|
+
if (p.killed && opts.force !== true) return true;
|
|
2797
2805
|
if (p.protected && opts.includeProtected !== true) return false;
|
|
2798
2806
|
if (opts.preserveBackground && p.background) return false;
|
|
2799
2807
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
2800
2808
|
const isWin2 = os.platform() === "win32";
|
|
2801
2809
|
if (isWin2) {
|
|
2802
|
-
const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
|
|
2810
|
+
const liveRealChild = p.child === null || p.child.exitCode === null && typeof p.child.pid === "number";
|
|
2803
2811
|
const directFallback = () => {
|
|
2804
|
-
if (p.child.exitCode === null) {
|
|
2812
|
+
if (p.child && p.child.exitCode === null) {
|
|
2805
2813
|
try {
|
|
2806
2814
|
p.child.kill("SIGKILL");
|
|
2807
2815
|
} catch {
|
|
@@ -2813,10 +2821,7 @@ var ProcessRegistryImpl = class {
|
|
|
2813
2821
|
onSettled: directFallback
|
|
2814
2822
|
})) {
|
|
2815
2823
|
} else {
|
|
2816
|
-
|
|
2817
|
-
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
2818
|
-
} catch {
|
|
2819
|
-
}
|
|
2824
|
+
this._killChildDirect(p, force ? "SIGKILL" : "SIGTERM");
|
|
2820
2825
|
}
|
|
2821
2826
|
p.killed = true;
|
|
2822
2827
|
return true;
|
|
@@ -2827,7 +2832,7 @@ var ProcessRegistryImpl = class {
|
|
|
2827
2832
|
} else {
|
|
2828
2833
|
this._killPosix(p, "SIGTERM");
|
|
2829
2834
|
const timer = setTimeout(() => {
|
|
2830
|
-
if (this.processes.has(pid) && !p.child
|
|
2835
|
+
if (this.processes.has(pid) && !p.child?.killed) {
|
|
2831
2836
|
this._killPosix(p, "SIGKILL");
|
|
2832
2837
|
}
|
|
2833
2838
|
}, graceMs);
|
|
@@ -2882,6 +2887,16 @@ var ProcessRegistryImpl = class {
|
|
|
2882
2887
|
* before reusing a PID, but we want to clean up before that becomes a risk.
|
|
2883
2888
|
*/
|
|
2884
2889
|
_isStaleEntry(entry) {
|
|
2890
|
+
if (entry.child === null) {
|
|
2891
|
+
if (Date.now() - entry.startedAt <= 6e4) return false;
|
|
2892
|
+
if (os.platform() === "win32") return false;
|
|
2893
|
+
try {
|
|
2894
|
+
process.kill(entry.pid, 0);
|
|
2895
|
+
return false;
|
|
2896
|
+
} catch (err) {
|
|
2897
|
+
return err.code !== "EPERM";
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2885
2900
|
return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
|
|
2886
2901
|
}
|
|
2887
2902
|
/**
|
package/dist/lint.js
CHANGED
|
@@ -364,8 +364,11 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
364
364
|
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
365
365
|
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
366
366
|
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
367
|
+
// The value must be token-like (>= 8 chars) so ordinary combined flags such
|
|
368
|
+
// as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
|
|
369
|
+
// redacted, not just the first.
|
|
367
370
|
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
368
|
-
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]
|
|
371
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
|
|
369
372
|
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
370
373
|
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
371
374
|
// redaction function. Synced with core copy.
|
|
@@ -373,8 +376,9 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
373
376
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
374
377
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
375
378
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
376
|
-
// when preceded by a flag name (e.g. --github-token=EyJ...).
|
|
377
|
-
|
|
379
|
+
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
380
|
+
// every such flag in the command line is redacted, not just the first.
|
|
381
|
+
/--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
|
|
378
382
|
];
|
|
379
383
|
function redactCommand(cmd) {
|
|
380
384
|
let result = cmd;
|
|
@@ -463,11 +467,15 @@ var ProcessRegistryImpl = class {
|
|
|
463
467
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
|
|
464
468
|
}
|
|
465
469
|
_canSignalProcessGroup(p) {
|
|
466
|
-
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
470
|
+
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && p.child !== null && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
467
471
|
}
|
|
468
472
|
_killChildDirect(p, signal) {
|
|
469
473
|
try {
|
|
470
|
-
p.child
|
|
474
|
+
if (p.child) {
|
|
475
|
+
p.child.kill(signal);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (this._isSafeSignalPid(p.pid)) process.kill(p.pid, signal);
|
|
471
479
|
} catch {
|
|
472
480
|
}
|
|
473
481
|
}
|
|
@@ -665,15 +673,15 @@ var ProcessRegistryImpl = class {
|
|
|
665
673
|
this._pruneStale(pid);
|
|
666
674
|
const p = this.processes.get(pid);
|
|
667
675
|
if (!p) return false;
|
|
668
|
-
if (p.killed) return true;
|
|
676
|
+
if (p.killed && opts.force !== true) return true;
|
|
669
677
|
if (p.protected && opts.includeProtected !== true) return false;
|
|
670
678
|
if (opts.preserveBackground && p.background) return false;
|
|
671
679
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
672
680
|
const isWin2 = os.platform() === "win32";
|
|
673
681
|
if (isWin2) {
|
|
674
|
-
const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
|
|
682
|
+
const liveRealChild = p.child === null || p.child.exitCode === null && typeof p.child.pid === "number";
|
|
675
683
|
const directFallback = () => {
|
|
676
|
-
if (p.child.exitCode === null) {
|
|
684
|
+
if (p.child && p.child.exitCode === null) {
|
|
677
685
|
try {
|
|
678
686
|
p.child.kill("SIGKILL");
|
|
679
687
|
} catch {
|
|
@@ -685,10 +693,7 @@ var ProcessRegistryImpl = class {
|
|
|
685
693
|
onSettled: directFallback
|
|
686
694
|
})) {
|
|
687
695
|
} else {
|
|
688
|
-
|
|
689
|
-
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
690
|
-
} catch {
|
|
691
|
-
}
|
|
696
|
+
this._killChildDirect(p, force ? "SIGKILL" : "SIGTERM");
|
|
692
697
|
}
|
|
693
698
|
p.killed = true;
|
|
694
699
|
return true;
|
|
@@ -699,7 +704,7 @@ var ProcessRegistryImpl = class {
|
|
|
699
704
|
} else {
|
|
700
705
|
this._killPosix(p, "SIGTERM");
|
|
701
706
|
const timer = setTimeout(() => {
|
|
702
|
-
if (this.processes.has(pid) && !p.child
|
|
707
|
+
if (this.processes.has(pid) && !p.child?.killed) {
|
|
703
708
|
this._killPosix(p, "SIGKILL");
|
|
704
709
|
}
|
|
705
710
|
}, graceMs);
|
|
@@ -754,6 +759,16 @@ var ProcessRegistryImpl = class {
|
|
|
754
759
|
* before reusing a PID, but we want to clean up before that becomes a risk.
|
|
755
760
|
*/
|
|
756
761
|
_isStaleEntry(entry) {
|
|
762
|
+
if (entry.child === null) {
|
|
763
|
+
if (Date.now() - entry.startedAt <= 6e4) return false;
|
|
764
|
+
if (os.platform() === "win32") return false;
|
|
765
|
+
try {
|
|
766
|
+
process.kill(entry.pid, 0);
|
|
767
|
+
return false;
|
|
768
|
+
} catch (err) {
|
|
769
|
+
return err.code !== "EPERM";
|
|
770
|
+
}
|
|
771
|
+
}
|
|
757
772
|
return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
|
|
758
773
|
}
|
|
759
774
|
/**
|
package/dist/logs.d.ts
CHANGED
package/dist/logs.js
CHANGED
|
@@ -126,6 +126,7 @@ function compileUserRegex(pattern, flags) {
|
|
|
126
126
|
var MAX_SUBJECT_LEN = 64 * 1024;
|
|
127
127
|
|
|
128
128
|
// src/_util.ts
|
|
129
|
+
import * as fsp from "node:fs/promises";
|
|
129
130
|
import * as path from "node:path";
|
|
130
131
|
import * as Core from "@wrongstack/core/utils";
|
|
131
132
|
function resolvePath(input, ctx) {
|
|
@@ -149,16 +150,50 @@ function ensureInsideRoot(absPath, ctx) {
|
|
|
149
150
|
function safeResolve(input, ctx) {
|
|
150
151
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
151
152
|
}
|
|
153
|
+
async function resolveRealInsideRoot(absPath, ctx) {
|
|
154
|
+
if (ctx.allowOutsideProjectRoot) return absPath;
|
|
155
|
+
const realRoots = await Promise.all(
|
|
156
|
+
allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
|
|
157
|
+
);
|
|
158
|
+
let probe = absPath;
|
|
159
|
+
const pendingTail = [];
|
|
160
|
+
for (; ; ) {
|
|
161
|
+
let real;
|
|
162
|
+
try {
|
|
163
|
+
real = await fsp.realpath(probe);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (err.code === "ENOENT") {
|
|
166
|
+
const parent = path.dirname(probe);
|
|
167
|
+
if (parent === probe) return absPath;
|
|
168
|
+
pendingTail.unshift(path.basename(probe));
|
|
169
|
+
probe = parent;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
if (isInsideAny(real, realRoots)) {
|
|
175
|
+
return pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
|
|
176
|
+
}
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function safeResolveReal(input, ctx) {
|
|
183
|
+
const abs = safeResolve(input, ctx);
|
|
184
|
+
return await resolveRealInsideRoot(abs, ctx);
|
|
185
|
+
}
|
|
152
186
|
|
|
153
187
|
// src/logs.ts
|
|
154
188
|
var logsTool = {
|
|
155
189
|
name: "logs",
|
|
156
190
|
category: "Logs",
|
|
157
|
-
description: "Read
|
|
158
|
-
usageHint: "DEBUGGING TOOL \u2014 USE CAREFULLY IN AUTONOMOUS MODE:\n\n- Prefer `path` for local files or `service` for containers
|
|
191
|
+
description: "Read logs from files or Docker containers. Useful for debugging running applications.",
|
|
192
|
+
usageHint: "DEBUGGING TOOL \u2014 USE CAREFULLY IN AUTONOMOUS MODE:\n\n- Prefer `path` for local files or `service` for Docker containers.\n- Always use `filter` (regex) when possible to reduce noise and token usage.\n- `since` narrows Docker logs to a recent window.",
|
|
159
193
|
permission: "confirm",
|
|
160
194
|
mutating: false,
|
|
161
195
|
timeoutMs: 3e4,
|
|
196
|
+
maxOutputBytes: 262144,
|
|
162
197
|
capabilities: ["shell.restricted"],
|
|
163
198
|
icon: "logs",
|
|
164
199
|
inputSchema: {
|
|
@@ -166,7 +201,7 @@ var logsTool = {
|
|
|
166
201
|
properties: {
|
|
167
202
|
service: {
|
|
168
203
|
type: "string",
|
|
169
|
-
description: "
|
|
204
|
+
description: "Docker container name (passed to `docker logs`)"
|
|
170
205
|
},
|
|
171
206
|
path: {
|
|
172
207
|
type: "string",
|
|
@@ -178,10 +213,6 @@ var logsTool = {
|
|
|
178
213
|
minimum: 0,
|
|
179
214
|
maximum: 1e4
|
|
180
215
|
},
|
|
181
|
-
stream: {
|
|
182
|
-
type: "boolean",
|
|
183
|
-
description: "Stream logs continuously (like tail -f) (default: false)"
|
|
184
|
-
},
|
|
185
216
|
filter: {
|
|
186
217
|
type: "string",
|
|
187
218
|
description: "Regex pattern to filter log lines"
|
|
@@ -189,7 +220,7 @@ var logsTool = {
|
|
|
189
220
|
since: {
|
|
190
221
|
type: "string",
|
|
191
222
|
enum: ["1h", "6h", "24h", "all"],
|
|
192
|
-
description:
|
|
223
|
+
description: 'Only show Docker logs since duration (ignored for files; "all" = no limit)'
|
|
193
224
|
},
|
|
194
225
|
cwd: { type: "string", description: "Working directory (default: cwd)" }
|
|
195
226
|
}
|
|
@@ -206,10 +237,10 @@ var logsTool = {
|
|
|
206
237
|
filterRe = compiled.regex;
|
|
207
238
|
}
|
|
208
239
|
if (input.service) {
|
|
209
|
-
return await dockerLogs(input.service, lines, filterRe, cwd, opts.signal);
|
|
240
|
+
return await dockerLogs(input.service, lines, filterRe, cwd, opts.signal, input.since);
|
|
210
241
|
}
|
|
211
242
|
if (input.path) {
|
|
212
|
-
return await fileLogs(
|
|
243
|
+
return await fileLogs(await safeResolveReal(input.path, ctx), lines, filterRe);
|
|
213
244
|
}
|
|
214
245
|
return {
|
|
215
246
|
source: "none",
|
|
@@ -223,7 +254,7 @@ var logsTool = {
|
|
|
223
254
|
async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
224
255
|
const args = ["logs"];
|
|
225
256
|
if (lines > 0) args.push("--tail", String(lines));
|
|
226
|
-
if (since) {
|
|
257
|
+
if (since && since !== "all") {
|
|
227
258
|
const sinceMap = { "1h": "1h", "6h": "6h", "24h": "24h" };
|
|
228
259
|
args.push("--since", sinceMap[since] ?? "1h");
|
|
229
260
|
}
|
|
@@ -288,7 +319,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
288
319
|
}
|
|
289
320
|
var DOCKER_LOGS_TIMEOUT_MS = 3e3;
|
|
290
321
|
var MAX_TAIL_LINES = 1e5;
|
|
291
|
-
async function fileLogs(path2, lines, filterRe
|
|
322
|
+
async function fileLogs(path2, lines, filterRe) {
|
|
292
323
|
const { createInterface } = await import("node:readline");
|
|
293
324
|
const { createReadStream } = await import("node:fs");
|
|
294
325
|
const entries = [];
|
|
@@ -322,7 +353,7 @@ async function fileLogs(path2, lines, filterRe, stream) {
|
|
|
322
353
|
entries,
|
|
323
354
|
total: entries.length,
|
|
324
355
|
truncated: totalLines > effLines,
|
|
325
|
-
stream_mode:
|
|
356
|
+
stream_mode: false
|
|
326
357
|
};
|
|
327
358
|
}
|
|
328
359
|
function parseLogLines(output, filterRe) {
|