@islamihab/kds 0.5.0 → 0.7.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.
Files changed (2) hide show
  1. package/index.js +177 -14
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -14420,9 +14420,11 @@ var printHelp = (help) => {
14420
14420
  description: `${field.description ?? ""}${isRequired(field) ? " (required)" : ""}`
14421
14421
  }));
14422
14422
  const usageOptions = options?.length ? " [options]" : "";
14423
- if (help.description)
14424
- console.log(`${help.description}
14423
+ if (help.description) {
14424
+ console.log(`${help.description}${help.longDescription ? `
14425
+ ${help.longDescription}` : ""}
14425
14426
  `);
14427
+ }
14426
14428
  console.log(`Usage: ${help.path}${usageCommands}${usageOptions}${usagePositionals}`);
14427
14429
  const labels = [commands, positionals, options].flatMap((entries) => entries ?? []);
14428
14430
  const width = Math.max(...labels.map(({ label }) => label.length));
@@ -14552,7 +14554,13 @@ var command = (def) => {
14552
14554
  const positionalEntries = Object.entries(inputShape.shape.positionals.shape);
14553
14555
  const optionsEntries = optionEntries(inputShape.shape.options.shape);
14554
14556
  const run = async (args, path) => {
14555
- const help = () => printHelp({ path, description: def.description, positionals: positionalEntries, options: optionsEntries });
14557
+ const help = () => printHelp({
14558
+ path,
14559
+ description: def.description,
14560
+ longDescription: def.longDescription,
14561
+ positionals: positionalEntries,
14562
+ options: optionsEntries
14563
+ });
14556
14564
  if (args.some(isHelpFlag))
14557
14565
  return help();
14558
14566
  const { values, positionals } = parseArgs({ args, positionalEntries, optionsEntries, help });
@@ -14570,7 +14578,13 @@ var group = (def) => {
14570
14578
  const optionsShape = exports_external.object(def.options);
14571
14579
  const optionsEntries = optionEntries(optionsShape.shape);
14572
14580
  const run = async ([sub, ...rest], path) => {
14573
- const help = () => printHelp({ path, description: def.description, commands: def.commands, options: optionsEntries });
14581
+ const help = () => printHelp({
14582
+ path,
14583
+ description: def.description,
14584
+ longDescription: def.longDescription,
14585
+ commands: def.commands,
14586
+ options: optionsEntries
14587
+ });
14574
14588
  if (!sub && !def.run || sub === "help" || isHelpFlag(sub))
14575
14589
  return help();
14576
14590
  if (def.run && (sub?.startsWith("-") || !sub)) {
@@ -14597,7 +14611,7 @@ import { join } from "path";
14597
14611
  // package.json
14598
14612
  var package_default = {
14599
14613
  name: "cli",
14600
- version: "0.5.0",
14614
+ version: "0.7.0",
14601
14615
  private: true,
14602
14616
  type: "module",
14603
14617
  bin: {
@@ -15188,6 +15202,14 @@ var ISSUE_OPEN_STATUSES = [
15188
15202
  "changes_requested",
15189
15203
  "approved"
15190
15204
  ];
15205
+ var REVIEW_FINDING_CATEGORIES = [
15206
+ "functional_correctness",
15207
+ "maintainability",
15208
+ "security",
15209
+ "performance"
15210
+ ];
15211
+ var REVIEW_FINDING_SEVERITIES = ["critical", "major", "minor", "trivial"];
15212
+ var MAX_REVIEW_FINDINGS = 50;
15191
15213
  var ISSUE_LABEL_COLORS = [
15192
15214
  "gray",
15193
15215
  "red",
@@ -18159,9 +18181,10 @@ var runInbox = async (options) => {
18159
18181
  };
18160
18182
 
18161
18183
  // src/lib/input.ts
18162
- var assertSize = (size) => {
18163
- if (size > MAX_PAGE_HTML_BYTES)
18164
- throw new Error(`Page is too large (${size} bytes; max ${MAX_PAGE_HTML_BYTES}).`);
18184
+ var assertSize = (size, subject = "Page") => {
18185
+ if (size > MAX_PAGE_HTML_BYTES) {
18186
+ throw new Error(`${subject} is too large (${size} bytes; max ${MAX_PAGE_HTML_BYTES}).`);
18187
+ }
18165
18188
  };
18166
18189
  var readBody = async (path) => {
18167
18190
  if (path === "-") {
@@ -18174,6 +18197,18 @@ var readBody = async (path) => {
18174
18197
  return await file2.text();
18175
18198
  };
18176
18199
  var readTextOption = async (value) => value === "-" ? await Bun.stdin.text() : value;
18200
+ var readFileOption = async (path) => {
18201
+ if (path === "-") {
18202
+ const text = await Bun.stdin.text();
18203
+ assertSize(new TextEncoder().encode(text).length, "Input");
18204
+ return text;
18205
+ }
18206
+ const file2 = Bun.file(path);
18207
+ if (!await file2.exists())
18208
+ throw new Error(`No file at ${path}.`);
18209
+ assertSize(file2.size, "Input");
18210
+ return await file2.text();
18211
+ };
18177
18212
  var readIssueRef = (value) => {
18178
18213
  const trimmed = value.trim();
18179
18214
  if (/^\d+$/.test(trimmed))
@@ -18389,7 +18424,7 @@ var comment = command({
18389
18424
  body: exports_external.string().describe("Comment markdown, or - for stdin")
18390
18425
  },
18391
18426
  options: {
18392
- attach: exports_external.array(exports_external.string()).optional().describe("Attach a file to the issue (repeatable)").meta({ short: "a" })
18427
+ attach: exports_external.array(exports_external.string()).optional().describe("Attach a file to the issue (repeatable); the printed URL is usable in issue markdown, and removal is dashboard-only").meta({ short: "a" })
18393
18428
  },
18394
18429
  run: async ({ positionals: { id, body }, options: { attach } }) => {
18395
18430
  const client3 = await backendClient();
@@ -18415,6 +18450,7 @@ var agentConfigurationLine = (id) => {
18415
18450
  var create = command({
18416
18451
  name: "create",
18417
18452
  description: "Create an issue and print its identifier",
18453
+ longDescription: "--disposition ready_for_agent requires --agent-config; the agent queue refuses an issue without one.",
18418
18454
  positionals: {
18419
18455
  title: exports_external.string().describe("Issue title")
18420
18456
  },
@@ -18698,6 +18734,37 @@ More issues match; raise --limit past ${options.limit}.`);
18698
18734
  }
18699
18735
  });
18700
18736
 
18737
+ // src/commands/issues/mark-addressed.ts
18738
+ var parseFindingRef = (value) => {
18739
+ const match = /^(.+):([1-9]\d*)$/.exec(value);
18740
+ if (!match?.[1] || !match[2])
18741
+ throw new Error(`--finding takes path:line, got "${value}".`);
18742
+ return { path: match[1], line: Number(match[2]) };
18743
+ };
18744
+ var markAddressed = command({
18745
+ name: "mark-addressed",
18746
+ description: "Mark review findings addressed by a commit and resolve their conversations",
18747
+ positionals: {
18748
+ id: exports_external.string().describe("Issue identifier, number, or URL")
18749
+ },
18750
+ options: {
18751
+ commit: exports_external.string().describe("The commit sha that addressed the findings").meta({ short: "c" }),
18752
+ finding: exports_external.array(exports_external.string()).optional().describe("Mark only this finding, as path:line (repeatable; default all)")
18753
+ },
18754
+ run: async ({ positionals: { id }, options: { commit, finding } }) => {
18755
+ const client3 = await backendClient();
18756
+ const issue2 = await resolveIssue(client3, id);
18757
+ const result = await client3.action(api2.githubReviewer.markAddressed, {
18758
+ id: issue2._id,
18759
+ commitSha: commit,
18760
+ findings: finding?.map(parseFindingRef)
18761
+ });
18762
+ const already = result.alreadyMarked.length ? ` (${result.alreadyMarked.length} already marked)` : "";
18763
+ console.log(`Marked ${result.marked.length} on ${result.identifier} as addressed in ${commit}${already}.`);
18764
+ console.log(result.resolved ? `Resolved ${result.resolved} conversation${result.resolved === 1 ? "" : "s"}.` : "All conversations already resolved.");
18765
+ }
18766
+ });
18767
+
18701
18768
  // src/commands/issues/relate.ts
18702
18769
  var relate = command({
18703
18770
  name: "relate",
@@ -18735,6 +18802,8 @@ var remove2 = command({
18735
18802
  var route = command({
18736
18803
  name: "route",
18737
18804
  description: "Route an issue to a disposition (wontfix also cancels it)",
18805
+ longDescription: `The disposition says who acts next; needs_triage is the default. Routing to
18806
+ ready_for_agent requires the issue to carry an agent configuration (set --agent-config).`,
18738
18807
  positionals: {
18739
18808
  id: exports_external.string().describe("Issue identifier, number, or URL"),
18740
18809
  disposition: exports_external.enum(ISSUE_DISPOSITIONS).describe("Where the issue goes next")
@@ -18758,6 +18827,9 @@ var route = command({
18758
18827
  var set2 = command({
18759
18828
  name: "set",
18760
18829
  description: "Set an issue's status, priority, estimate, due date, project, milestone, parent, agent configuration, or labels",
18830
+ longDescription: `Status normally moves through the workflow commands and the linked pull request;
18831
+ --status is the correction path. Clearing the agent configuration is refused while
18832
+ the issue sits in ready_for_agent.`,
18761
18833
  positionals: {
18762
18834
  id: exports_external.string().describe("Issue identifier, number, or URL")
18763
18835
  },
@@ -18832,6 +18904,8 @@ var shellArg = (value) => /^[\w./-]+$/.test(value) ? value : `'${value.replaceAl
18832
18904
  var start = command({
18833
18905
  name: "start",
18834
18906
  description: "Claim implementation work: move unstarted or changes_requested work to in_progress with its branch",
18907
+ longDescription: `Starting never routes the issue: the note printed when the disposition is not
18908
+ ready_for_agent is informational, and --ready belongs only to an explicit hand-off.`,
18835
18909
  positionals: {
18836
18910
  id: exports_external.string().describe("Issue identifier, number, or URL")
18837
18911
  },
@@ -18859,15 +18933,16 @@ var start = command({
18859
18933
  // src/commands/issues/start-review.ts
18860
18934
  var startReview = command({
18861
18935
  name: "start-review",
18862
- description: "Claim review work: move a ready_for_review issue to in_review",
18936
+ description: "Claim review work: move a ready_for_review issue to in_review and announce the review on its pull request",
18863
18937
  positionals: {
18864
18938
  id: exports_external.string().describe("Issue identifier, number, or URL")
18865
18939
  },
18866
18940
  run: async ({ positionals: { id } }) => {
18867
18941
  const client3 = await backendClient();
18868
18942
  const issue2 = await resolveIssue(client3, id);
18869
- await client3.mutation(api2.issues.startReview, { id: issue2._id });
18870
- console.log(`Reviewing ${issue2.identifier}: in_review`);
18943
+ const started = await client3.action(api2.githubReviewer.startReview, { id: issue2._id });
18944
+ console.log(`Reviewing ${started.identifier}: in_review`);
18945
+ console.log(`Announced on ${started.prUrl}; check run open.`);
18871
18946
  }
18872
18947
  });
18873
18948
 
@@ -18886,11 +18961,81 @@ var submit = command({
18886
18961
  }
18887
18962
  });
18888
18963
 
18964
+ // src/commands/issues/submit-review.ts
18965
+ var findingsSchema = exports_external.array(exports_external.object({
18966
+ path: exports_external.string().min(1),
18967
+ line: exports_external.number().int().positive(),
18968
+ startLine: exports_external.number().int().positive().optional(),
18969
+ category: exports_external.enum(REVIEW_FINDING_CATEGORIES),
18970
+ severity: exports_external.enum(REVIEW_FINDING_SEVERITIES),
18971
+ summary: exports_external.string().min(1),
18972
+ body: exports_external.string().min(1),
18973
+ diff: exports_external.string().optional()
18974
+ })).min(1).max(MAX_REVIEW_FINDINGS);
18975
+ var readFindings = async (path) => {
18976
+ const text = await readFileOption(path);
18977
+ let json2;
18978
+ try {
18979
+ json2 = JSON.parse(text);
18980
+ } catch {
18981
+ throw new Error("The findings file is not valid JSON.");
18982
+ }
18983
+ const parsed = findingsSchema.safeParse(json2);
18984
+ if (!parsed.success)
18985
+ throw new Error(`The findings file is invalid:
18986
+ ${exports_external.prettifyError(parsed.error)}`);
18987
+ return parsed.data;
18988
+ };
18989
+ var submitReview = command({
18990
+ name: "submit-review",
18991
+ description: "Submit the review verdict on an issue's linked pull request as the reviewer App",
18992
+ longDescription: `Findings are a JSON array (at most ${MAX_REVIEW_FINDINGS}) of objects with path, line, category,
18993
+ severity, summary, and body, plus optional startLine and diff.
18994
+ Categories: ${REVIEW_FINDING_CATEGORIES.join(", ")}; severities: ${REVIEW_FINDING_SEVERITIES.join(", ")}.`,
18995
+ positionals: {
18996
+ id: exports_external.string().describe("Issue identifier, number, or URL")
18997
+ },
18998
+ options: {
18999
+ approve: exports_external.boolean().default(false).describe("Approve: bare APPROVE plus a clean bill"),
19000
+ "request-changes": exports_external.boolean().default(false).describe("Request changes from a findings file"),
19001
+ findings: exports_external.string().optional().describe("Findings JSON file, or - for stdin (with --request-changes)"),
19002
+ bill: exports_external.string().optional().describe("Clean-bill text, or - for stdin (with --approve)")
19003
+ },
19004
+ run: async ({ positionals: { id }, options }) => {
19005
+ if (options.approve === options["request-changes"]) {
19006
+ throw new Error("Pass exactly one of --approve or --request-changes.");
19007
+ }
19008
+ if (options.approve && options.findings !== undefined)
19009
+ throw new Error("--findings goes with --request-changes.");
19010
+ if (options["request-changes"] && options.bill !== undefined)
19011
+ throw new Error("--bill goes with --approve.");
19012
+ let verdict;
19013
+ if (options.approve) {
19014
+ if (options.bill === undefined)
19015
+ throw new Error("--approve needs the clean bill: --bill <text>, or - for stdin.");
19016
+ verdict = { kind: "approve", bill: await readTextOption(options.bill) };
19017
+ } else {
19018
+ if (options.findings === undefined) {
19019
+ throw new Error("--request-changes needs the findings: --findings <file>, or - for stdin.");
19020
+ }
19021
+ verdict = { kind: "request_changes", findings: await readFindings(options.findings) };
19022
+ }
19023
+ const client3 = await backendClient();
19024
+ const issue2 = await resolveIssue(client3, id);
19025
+ const submitted = await client3.action(api2.githubReviewer.submitReview, { id: issue2._id, verdict });
19026
+ console.log(`Submitted review on ${submitted.identifier}: ${submitted.prUrl}`);
19027
+ console.log(submitted.summary);
19028
+ }
19029
+ });
19030
+
18889
19031
  // src/commands/issues/tasks.ts
18890
19032
  var position = exports_external.array(exports_external.coerce.number().int().positive());
18891
19033
  var tasks = command({
18892
19034
  name: "tasks",
18893
19035
  description: "Show an issue's checklist, or add, tick, promote, and drop its tasks",
19036
+ longDescription: `Tasks have no identifier, status, disposition, or feed, and ticking one files no
19037
+ activity. Task numbers resolve against the listing as printed, so one call may
19038
+ address several rows even as removals renumber it.`,
18894
19039
  positionals: {
18895
19040
  id: exports_external.string().describe("Issue identifier, number, or URL")
18896
19041
  },
@@ -18898,7 +19043,7 @@ var tasks = command({
18898
19043
  add: exports_external.array(exports_external.string()).optional().describe("Add a task (repeatable)").meta({ short: "a" }),
18899
19044
  check: position.optional().describe("Tick a task by its number (repeatable)"),
18900
19045
  uncheck: position.optional().describe("Untick a task by its number (repeatable)"),
18901
- convert: position.optional().describe("Promote a task into its own issue (repeatable)"),
19046
+ convert: position.optional().describe("Promote a task into a sub-issue (repeatable); the new issue inherits the project and milestone, arrives in needs_triage, and consumes the task \u2014 a related issue instead when this issue is already a sub-issue"),
18902
19047
  remove: position.optional().describe("Drop a task by its number (repeatable)"),
18903
19048
  json: exports_external.boolean().default(false).describe("Print as JSON")
18904
19049
  },
@@ -18987,7 +19132,7 @@ var update2 = command({
18987
19132
  options: {
18988
19133
  title: exports_external.string().optional().describe("New title").meta({ short: "t" }),
18989
19134
  description: exports_external.string().optional().describe("New markdown description, or - for stdin").meta({ short: "d" }),
18990
- attach: exports_external.array(exports_external.string()).optional().describe("Attach a file to the issue (repeatable)").meta({ short: "a" })
19135
+ attach: exports_external.array(exports_external.string()).optional().describe("Attach a file to the issue (repeatable); the printed URL is usable in issue markdown, and removal is dashboard-only").meta({ short: "a" })
18991
19136
  },
18992
19137
  run: async ({ positionals: { id }, options: { title, description, attach } }) => {
18993
19138
  if (title === undefined && description === undefined && attach === undefined)
@@ -19010,6 +19155,16 @@ var update2 = command({
19010
19155
  var issues = group({
19011
19156
  name: "issues",
19012
19157
  description: "Track and triage issues",
19158
+ longDescription: `An issue moves on three independent axes: status (the lifecycle, in order: backlog,
19159
+ todo, in_progress, ready_for_review, in_review, changes_requested, approved, done,
19160
+ canceled), priority, and disposition (who acts next). The workflow commands \u2014 start,
19161
+ submit, start-review, submit-review \u2014 and the linked pull request move the status;
19162
+ each refuses an issue outside its own status, and \`set -s\` is for corrections.
19163
+ The repo link is never set by hand: work on a branch whose name contains the issue
19164
+ identifier and the branch, pull request, and status track automatically \u2014 a change
19165
+ request moves the issue to changes_requested, an approval to approved (one
19166
+ outstanding change request outweighs any number of approvals), and a merge lands
19167
+ it as done.`,
19013
19168
  commands: [
19014
19169
  create,
19015
19170
  list2,
@@ -19021,6 +19176,8 @@ var issues = group({
19021
19176
  start,
19022
19177
  submit,
19023
19178
  startReview,
19179
+ submitReview,
19180
+ markAddressed,
19024
19181
  tasks,
19025
19182
  relate,
19026
19183
  unrelate,
@@ -19217,6 +19374,10 @@ var versions2 = command({
19217
19374
  var pages = group({
19218
19375
  name: "pages",
19219
19376
  description: "Publish HTML documents to the web",
19377
+ longDescription: `Themed pages (the default) render inside the site chrome under a strict CSP: only
19378
+ the site theme's styles apply \u2014 inline style attributes and author scripts are
19379
+ dropped. A self-contained document with its own CSS or JS needs raw mode, which
19380
+ serves it verbatim. A page keeps its URL across updates and reverts.`,
19220
19381
  commands: [create3, list3, get2, update3, versions2, revert, remove3]
19221
19382
  });
19222
19383
 
@@ -19432,6 +19593,8 @@ var update4 = command({
19432
19593
  var projects = group({
19433
19594
  name: "projects",
19434
19595
  description: "Manage projects",
19596
+ longDescription: `Connect a repository (set --repo) to make --here resolve in that checkout and to
19597
+ let issue branches and pull requests link automatically.`,
19435
19598
  commands: [create4, list4, get3, update4, set3, remove4]
19436
19599
  });
19437
19600
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@islamihab/kds",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Command-line client for Kai Dev Studio",
5
5
  "license": "MIT",
6
6
  "publishConfig": {