@difflab/difflab-cli 0.8.1 → 0.9.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 (3) hide show
  1. package/README.md +6 -0
  2. package/dist/index.js +326 -88
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -72,6 +72,12 @@ On first use, missing bundled templates and filled examples are copied to `~/.di
72
72
 
73
73
  The portable `skills/difflab-plan`, `skills/difflab-review`, `skills/difflab-flow`, `skills/difflab-explore`, `skills/difflab-adr`, and `skills/difflab-poc` directories contain natural-language agent workflows for these templates. Install each skill in your client's Agent Skills directory. The MCP-only `log_append({"path":"/absolute/physical/project-store/plans/YYMMDD-name/logs.txt","message":"Phase started"})` tool creates an append-only progress log on first use; pass the physical path, not the `.difflab` symlink. There is no log CLI command.
74
74
 
75
+ The [plan authoring guide](../../skills/difflab-plan/references/authoring.md) requires component/process/artifact visuals, compact interfaces, and a nested repo tree at the start of `Consequences`. Add a `Testing` outline of exact end-to-end and integration scenarios. Keep expected commit and green-CI gates, including for `plan go --commit`. That flag permits scoped commits. Push requires separate authorization. Write `N/A.` when infrastructure stays unchanged. Source examples live in the skill's `references/examples/` and are not CLI templates.
76
+
77
+ The [review rubric](../../skills/difflab-review/references/rubric.md) follows language conventions, codebase patterns, and applicable `AGENTS.md` rules. Refactor unclear code at the scope needed. Review application behavior first, then module public APIs, then focused unit behavior. Require clear Arrange/Act/Assert blocks and useful fixtures. Untested private functions alone are not findings. Keep comments short and use simple English. Omit nitpicks and minor optimizations. Explicit security/performance focuses deepen those audits.
78
+
79
+ For an upgrade, reinstall changed skills and reload the client. Back up and move aside only the installed templates/examples you want refreshed, then list templates again. For these changes, inspect `~/.difflab/templates/{spec-driven-plan,code-review}.md` and `examples/{plan-feature,plan-migration,review-change}.md`. Preserve customizations in backups and merge deliberately; never reset the whole directory. Older templates still work: skills add missing subsections to generated documents while preserving human content and existing phase completion state.
80
+
75
81
  ## Flow definitions and runs
76
82
 
77
83
  `flow new <name>` and `flow go <name>` are Agent Skill requests, not CLI subcommands. The agent creates a definition once at `~/.difflab/flows/<name>.md`. It calls MCP `scaffold` with `cwd=<absolute home>/.difflab`, `path="flows"`, and `filename="<name>.md"`. Do not use `cwd=<home>,path=".difflab/flows"`: that route is for a repository project store. You can edit a definition before another run. A new run uses a fresh snapshot and does not alter old runs.
package/dist/index.js CHANGED
@@ -37320,6 +37320,37 @@ var require_utils = __commonJS(function(exports, module) {
37320
37320
  }
37321
37321
  return output.join("");
37322
37322
  }
37323
+ function removeDotSegmentsRelative(path) {
37324
+ if (path === "" || path[0] === "/") {
37325
+ return removeDotSegments(path);
37326
+ }
37327
+ const segments = path.split("/");
37328
+ const last = segments.length - 1;
37329
+ const output = [];
37330
+ let trailingDirectory = false;
37331
+ for (let i = 0;i <= last; i++) {
37332
+ const segment = segments[i];
37333
+ if (segment === "." || segment === "..") {
37334
+ if (segment === "..") {
37335
+ if (output.length !== 0 && output[output.length - 1] !== "..") {
37336
+ output.pop();
37337
+ } else {
37338
+ output.push("..");
37339
+ }
37340
+ }
37341
+ trailingDirectory = i === last;
37342
+ } else {
37343
+ output.push(segment);
37344
+ }
37345
+ }
37346
+ if (trailingDirectory && output[output.length - 1] !== "..") {
37347
+ output.push("");
37348
+ }
37349
+ if (output.length === 0 || output[0] === "" || output[0].includes(":")) {
37350
+ output.unshift(".");
37351
+ }
37352
+ return output.join("/");
37353
+ }
37323
37354
  var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
37324
37355
  var HOST_DELIM_RE = /[@/?#:]/g;
37325
37356
  var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
@@ -37580,6 +37611,7 @@ var require_utils = __commonJS(function(exports, module) {
37580
37611
  encodeFragment,
37581
37612
  escapePreservingEscapes,
37582
37613
  removeDotSegments,
37614
+ removeDotSegmentsRelative,
37583
37615
  isIPv4,
37584
37616
  isUUID,
37585
37617
  normalizeIPv6,
@@ -37764,16 +37796,18 @@ var require_schemes = __commonJS(function(exports, module) {
37764
37796
 
37765
37797
  // ../../node_modules/fast-uri/index.js
37766
37798
  var require_fast_uri = __commonJS(function(exports, module) {
37767
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
37799
+ var { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
37768
37800
  var { SCHEMES, getSchemeHandler } = require_schemes();
37769
37801
  var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
37770
37802
  var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
37771
- function decodeValidScheme(scheme) {
37772
- const decodedScheme = unescape(String(scheme));
37773
- if (!VALID_SCHEME.test(decodedScheme)) {
37803
+ var MALFORMED_HOST_ERROR = "URI host is malformed.";
37804
+ var FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u;
37805
+ function validateScheme(scheme) {
37806
+ const stringScheme = String(scheme);
37807
+ if (!VALID_SCHEME.test(stringScheme)) {
37774
37808
  throw new TypeError(MALFORMED_SCHEME_ERROR);
37775
37809
  }
37776
- return decodedScheme;
37810
+ return stringScheme;
37777
37811
  }
37778
37812
  function normalize(uri, options) {
37779
37813
  if (typeof uri === "string") {
@@ -37808,6 +37842,9 @@ var require_fast_uri = __commonJS(function(exports, module) {
37808
37842
  const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
37809
37843
  const resolvedHost = resolved.host;
37810
37844
  const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
37845
+ if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
37846
+ throw new Error(MALFORMED_HOST_ERROR);
37847
+ }
37811
37848
  canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
37812
37849
  const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
37813
37850
  if (resolved.error && !encodedASCIIHost) {
@@ -37849,14 +37886,15 @@ var require_fast_uri = __commonJS(function(exports, module) {
37849
37886
  if (relative.path[0] === "/") {
37850
37887
  target.path = removeDotSegments(relative.path);
37851
37888
  } else {
37852
- if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) {
37889
+ const baseHasAuthority = base.userinfo !== undefined || base.host !== undefined || base.port !== undefined;
37890
+ if (baseHasAuthority && !base.path) {
37853
37891
  target.path = "/" + relative.path;
37854
37892
  } else if (!base.path) {
37855
37893
  target.path = relative.path;
37856
37894
  } else {
37857
37895
  target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
37858
37896
  }
37859
- target.path = removeDotSegments(target.path);
37897
+ target.path = base.scheme === undefined && !baseHasAuthority && target.path[0] !== "/" ? removeDotSegmentsRelative(target.path) : removeDotSegments(target.path);
37860
37898
  }
37861
37899
  target.query = relative.query;
37862
37900
  }
@@ -37894,13 +37932,14 @@ var require_fast_uri = __commonJS(function(exports, module) {
37894
37932
  const options = Object.assign({}, opts);
37895
37933
  const uriTokens = [];
37896
37934
  if (component.scheme) {
37897
- component.scheme = decodeValidScheme(component.scheme);
37935
+ component.scheme = validateScheme(component.scheme);
37898
37936
  }
37899
37937
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
37900
37938
  if (schemeHandler && schemeHandler.serialize)
37901
37939
  schemeHandler.serialize(component, options);
37902
37940
  const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined;
37903
- const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority;
37941
+ const schemelessReference = (component.scheme === undefined || options.reference === "suffix") && !hasAuthority;
37942
+ const pathNoScheme = !options.skipEscape && schemelessReference;
37904
37943
  if (component.path !== undefined) {
37905
37944
  if (!options.skipEscape) {
37906
37945
  component.path = serializePathEncoding(component.path, pathNoScheme);
@@ -37909,7 +37948,7 @@ var require_fast_uri = __commonJS(function(exports, module) {
37909
37948
  }
37910
37949
  }
37911
37950
  if (options.reference !== "suffix" && component.scheme) {
37912
- component.scheme = decodeValidScheme(component.scheme);
37951
+ component.scheme = validateScheme(component.scheme);
37913
37952
  uriTokens.push(component.scheme, ":");
37914
37953
  }
37915
37954
  const authority = recomposeAuthority(component);
@@ -37925,9 +37964,9 @@ var require_fast_uri = __commonJS(function(exports, module) {
37925
37964
  if (component.path !== undefined) {
37926
37965
  let s = component.path;
37927
37966
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
37928
- s = removeDotSegments(s);
37967
+ s = schemelessReference && s[0] !== "/" ? removeDotSegmentsRelative(s) : removeDotSegments(s);
37929
37968
  }
37930
- if (pathNoScheme) {
37969
+ if (schemelessReference) {
37931
37970
  s = serializePathEncoding(s, true);
37932
37971
  }
37933
37972
  if (authority === undefined && s[0] === "/" && s[1] === "/") {
@@ -37975,16 +38014,32 @@ var require_fast_uri = __commonJS(function(exports, module) {
37975
38014
  return hasMalformedPercentEncoding(matches[3]) || host !== undefined && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
37976
38015
  }
37977
38016
  function canonicalizeHost(parsed, options, schemeHandler, isIP) {
38017
+ if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
38018
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
38019
+ return true;
38020
+ }
37978
38021
  if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
38022
+ let url;
37979
38023
  try {
37980
- parsed.host = new URL("http://" + parsed.host).hostname;
38024
+ url = new URL("http://" + parsed.host);
37981
38025
  } catch (e) {
37982
38026
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
37983
38027
  return true;
37984
38028
  }
38029
+ if (!isHostOnlyURL(url)) {
38030
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
38031
+ return true;
38032
+ }
38033
+ parsed.host = url.hostname;
37985
38034
  }
37986
38035
  return false;
37987
38036
  }
38037
+ function hasForbiddenDomainHost(parsed, options, schemeHandler, isIP) {
38038
+ return Boolean(parsed.host && isIP === false && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host));
38039
+ }
38040
+ function isHostOnlyURL(url) {
38041
+ return url.username === "" && url.password === "" && url.port === "" && url.pathname === "/" && url.search === "" && url.hash === "" && url.hostname !== "";
38042
+ }
37988
38043
  function parseWithStatus(uri, opts) {
37989
38044
  const options = Object.assign({}, opts);
37990
38045
  const parsed = {
@@ -38039,9 +38094,8 @@ var require_fast_uri = __commonJS(function(exports, module) {
38039
38094
  parsed.query = matches[7];
38040
38095
  parsed.fragment = matches[8];
38041
38096
  if (parsed.scheme !== undefined) {
38042
- const decodedScheme = unescape(parsed.scheme);
38043
- if (VALID_SCHEME.test(decodedScheme)) {
38044
- parsed.scheme = decodedScheme.toLowerCase();
38097
+ if (VALID_SCHEME.test(parsed.scheme)) {
38098
+ parsed.scheme = parsed.scheme.toLowerCase();
38045
38099
  } else {
38046
38100
  parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
38047
38101
  malformedScheme = true;
@@ -38069,7 +38123,7 @@ var require_fast_uri = __commonJS(function(exports, module) {
38069
38123
  malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
38070
38124
  parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
38071
38125
  if (malformedIPLiteral) {
38072
- parsed.error = parsed.error || "URI host is malformed.";
38126
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
38073
38127
  malformedAuthorityOrPort = true;
38074
38128
  }
38075
38129
  } else {
@@ -38097,7 +38151,7 @@ var require_fast_uri = __commonJS(function(exports, module) {
38097
38151
  if (!isIP) {
38098
38152
  host = normalizePercentEncoding(host.toLowerCase());
38099
38153
  }
38100
- parsed.host = reescapeHostDelimiters(host, isIP);
38154
+ parsed.host = isIP || options.scheme !== "null" ? reescapeHostDelimiters(host, isIP) : host;
38101
38155
  }
38102
38156
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
38103
38157
  if (parsed.path) {
@@ -48308,23 +48362,33 @@ var spec_driven_plan_default = `# [Outcome to deliver]
48308
48362
 
48309
48363
  ### Big Ideas
48310
48364
 
48311
- [Describe the intended behavior and the main architectural choices.]
48365
+ [Show changed component relationships with a component diagram, new processes with flowcharts, and new file-based artifacts with trees or suitable visuals. Use Mermaid and fenced text where useful. Keep decisions, responsibilities, invariants, and important failure behavior beside the relevant visual. Draw only the affected parts; keep enough detail to execute without the original chat.]
48312
48366
 
48313
48367
  ### Infrastructure Changes
48314
48368
 
48315
- [Any infra changes. Optional. Only include if there's actual infra being handled in repo.]
48369
+ [Actual hosting, services, deployment, networking, or managed-resource changes. New source modules belong in Repo Changes. Write N/A. if no infrastructure changes.]
48316
48370
 
48317
48371
  ### Interfaces
48318
48372
 
48319
- [CLI commands, API endpoint, data contracts, db entities, modules, apps, libs, project tasks added, changed or removed. Summary of how a user's interaction with the app/lib/cli/sdk/etc. will change as a result.]
48373
+ [Use compact commands, signatures, code, or pseudocode to show changed public interactions and contracts, including important failure results. Keep implementation actions in the phases.]
48320
48374
 
48321
48375
  ### Consequences
48322
48376
 
48323
- [Trade-offs, risks, compatibility, and what is deferred.]
48377
+ #### Repo Changes
48378
+
48379
+ [Start with a selected file tree: + new, ~ changed, - removed, * largest edits. Include new modules and meaningful structural changes. Use real inspected paths; label proposed files. State when nothing is removed.]
48380
+
48381
+ #### Testing
48382
+
48383
+ [Show a nested outline of end-to-end and integration test scenarios, grouped by feature and behavior. Name the entry point, input/action, and expected result for each scenario. Reuse existing fixtures and suites. Match the scenarios to requirements and phase checks.]
48384
+
48385
+ #### Trade-offs
48386
+
48387
+ [Risks, compatibility, and what is deferred.]
48324
48388
 
48325
48389
  ## Phases
48326
48390
 
48327
- [Instructions broken down granularly, specifying modules/classes/methods/files to be added/updated/removed, and relevant technical work done with terse reasoning. Terse for human readers, sufficiently detailed for delegating implementation to less capable language models i.e. a fool proof guide for a junior developer.]
48391
+ [Ordered tasks with file/module targets and enough detail to act. Keep decisions and contracts in the design. Specify the exact application behavior for end-to-end tests and the public module behavior for integration tests. Require clear Arrange/Act/Assert blocks. Use relevant checks and existing helpers. Keep conditional commit and CI gates when expected, including for plan go --commit. Follow the execution workflow's commit/push permissions.]
48328
48392
 
48329
48393
  ### Phase 1: [Outcome]
48330
48394
 
@@ -48333,8 +48397,9 @@ var spec_driven_plan_default = `# [Outcome to deliver]
48333
48397
  - [ ] [Task C]
48334
48398
  - [ ] [Task D]
48335
48399
  - [ ] Validation
48336
- - [ ] Lint, format, etc.
48337
- - [ ] Commit, CI green (if expected)
48400
+ - [ ] [Observable application/module contract and the existing or proposed check.]
48401
+ - [ ] [Relevant format, lint, or build checks.]
48402
+ - [ ] [Commit and confirm CI is green, if expected and authorized.]
48338
48403
 
48339
48404
  ### Phase 2: [Outcome]
48340
48405
 
@@ -48343,8 +48408,9 @@ var spec_driven_plan_default = `# [Outcome to deliver]
48343
48408
  - [ ] [Task C]
48344
48409
  - [ ] [Task D]
48345
48410
  - [ ] Validation
48346
- - [ ] Lint, format, etc.
48347
- - [ ] Commit, CI green (if expected)
48411
+ - [ ] [Observable application/module contract and the existing or proposed check.]
48412
+ - [ ] [Relevant format, lint, or build checks.]
48413
+ - [ ] [Commit and confirm CI is green, if expected and authorized.]
48348
48414
 
48349
48415
  ### Phase 3: [Outcome]
48350
48416
 
@@ -48353,8 +48419,9 @@ var spec_driven_plan_default = `# [Outcome to deliver]
48353
48419
  - [ ] [Task C]
48354
48420
  - [ ] [Task D]
48355
48421
  - [ ] Validation
48356
- - [ ] Lint, format, etc.
48357
- - [ ] Commit, CI green (if expected)
48422
+ - [ ] [Observable application/module contract and the existing or proposed check.]
48423
+ - [ ] [Relevant format, lint, or build checks.]
48424
+ - [ ] [Commit and confirm CI is green, if expected and authorized.]
48358
48425
 
48359
48426
  ## References
48360
48427
 
@@ -48564,31 +48631,35 @@ var code_review_default = `# Review: [change or pull request]
48564
48631
 
48565
48632
  ## Readability
48566
48633
 
48567
- [Can a new reader follow the flow, names, and error paths? Is code readable for and understandable for an average mide-level developer at a glance?]
48634
+ [Do names and code structure show what happens? Compare names with behavior, side effects, and callers. Follow language conventions and established codebase patterns. Flag misleading names and flows that are hard to follow.]
48568
48635
 
48569
48636
  ## Semantic Organization
48570
48637
 
48571
- [Are responsibilities, boundaries, and public APIs arranged by meaning? Are modules semantically meaningful? Is code in applications and libraries well sliced by concerns?]
48638
+ [Do modules have clear roles and useful public APIs? Identify mixed responsibilities and unclear ownership. Refactor or simplify at the scope needed to make the change understandable, including across files. Keep unrelated work out of scope.]
48572
48639
 
48573
48640
  ## Maintainability
48574
48641
 
48575
- [Is the code DRY? Is the code over-engineered beyond required scope? Are well trusted / industry-standard libraries and frameworks used for standard tasks? Are domain specific conventions approprate for the codebase being followed? Is basic linting, formatting and static analysis hygiene present?]
48642
+ [Does the change follow established codebase patterns and applicable AGENTS.md rules? Check new fields, types, helpers, and dependencies against existing code and callers. Reuse suitable code and remove needless complexity.]
48576
48643
 
48577
48644
  ## Reliability/Testing
48578
48645
 
48579
- [Behavior, edge cases, test coverage, and evidence from executed checks. Adhere to repo testing standards if specified in AGENTS.md or docs. Otherwise, generally prioritize e2e coverage and test readability and organization.]
48646
+ [Prioritize end-to-end application behavior, then module behavior through public APIs, then focused unit behavior with public APIs first. Name missing observable contracts or regressions; do not demand coverage of every function or a percentage. Check semantic grouping, clear Arrange/Act/Assert blocks, fixtures, and existing shared helpers. Flag walls of repeated setup or interleaved actions/assertions that hide the behavior. Report executed checks and limits honestly.]
48647
+
48648
+ ## Repository Guidance
48649
+
48650
+ [List root/applicable nested AGENTS.md paths and material deviations in changed files. Cite actual rules, including code/test organization and helper placement where prescribed. If none applies or guidance could not be read, say so. Keep this assessment brief.]
48580
48651
 
48581
48652
  ## Performance
48582
48653
 
48583
- [Cost, allocations, I/O, scaling, and measured or expected impact. State SKIPPED with reasons for non-perf bugfixes, minor refactors, and non-performance critical applications.]
48654
+ [Report material regressions with evidence. Avoid nitpicks and minor optimizations. Deepen the audit for an explicit performance focus. Otherwise mark a deeper audit SKIPPED with a short reason.]
48584
48655
 
48585
48656
  ## Security
48586
48657
 
48587
- [Evaluate authentication and autorization, permissions, secrets, dependencies, input validation, proper error handling, and trust boundaries. State SKIPPED if skipped with reason, for example for chores, simple scripting, pure cosmetic UI work, documentation, etc.]
48658
+ [Report clear security defects, including exposed secrets or broken authorization/trust boundaries. Deepen the audit for an explicit security focus; avoid unrelated hardening requests. State SKIPPED with a short reason when a deeper audit is not warranted.]
48588
48659
 
48589
48660
  ## Change Requests
48590
48661
 
48591
- Track actionable inline requests with one entry per request. End each generated request and response with its own model attribution quote; preserve human text unchanged.
48662
+ Use short comments in simple English. State the problem and its effect directly. Phrase recommendations as suggestions with \u201CLet's\u201D, \u201CI suggest\u201D, or \u201CI recommend\u201D, usually in one to three sentences total. Use the scope that fixes the problem, including a refactor when needed. Group repeated instances of one cause. Omit nitpicks, minor optimizations, and speculative findings. End each generated request and response with its model attribution quote. Preserve human text unchanged.
48592
48663
 
48593
48664
  ### [file:line \u2014 request]
48594
48665
 
@@ -48603,13 +48674,13 @@ Track actionable inline requests with one entry per request. End each generated
48603
48674
 
48604
48675
  ## Findings
48605
48676
 
48606
- | Severity | Location | Evidence and impact | Required change or question |
48607
- | ------------------------ | ----------- | -------------------------- | --------------------------- |
48608
- | [blocking/consider/note] | [file:line] | [reproducible observation] | [concrete action] |
48677
+ | Severity | Location | Evidence and impact | Required change or question |
48678
+ | --- | --- | --- | --- |
48679
+ | [blocking/consider/note] | [file:line] | [reproducible observation] | [concrete action] |
48609
48680
 
48610
48681
  ## Overall Assessment
48611
48682
 
48612
- [Recommendation and reasons; distinguish findings from preferences.]
48683
+ [Recommendation and reasons; distinguish material findings from preferences. A clean review may have no change requests. Keep section assessments concise.]
48613
48684
 
48614
48685
  ## Verification and Limits
48615
48686
 
@@ -48726,11 +48797,11 @@ var flow_definition_default = `# {{name}}
48726
48797
 
48727
48798
  ## Flags
48728
48799
 
48729
- | Flag | Default | Capability | Effect |
48730
- | --------------- | ------- | ------------- | ---------------------------------------------------------------------------------- |
48731
- | --local | false | local | Omit steps with \`unless: --local\`; never forward to every skill. |
48732
- | --commit | false | local-commit | Permit only explicitly commit-capable selected steps to make scoped local commits. |
48733
- | --remote-review | false | remote-review | Opt in to remote review with independent forge authorization. |
48800
+ | Flag | Default | Capability | Effect |
48801
+ | --- | --- | --- | --- |
48802
+ | --local | false | local | Omit steps with \`unless: --local\`; never forward to every skill. |
48803
+ | --commit | false | local-commit | Permit only explicitly commit-capable selected steps to make scoped local commits. |
48804
+ | --remote-review | false | remote-review | Opt in to remote review with independent forge authorization. |
48734
48805
 
48735
48806
  ## Steps
48736
48807
 
@@ -48922,11 +48993,11 @@ base_commit: '{{full SHA of origin/main at branch creation}}'
48922
48993
 
48923
48994
  {{Tool versions, dependency installation, environment inputs, and required services.}}
48924
48995
 
48925
- | Task | Command | Purpose | Observed result |
48926
- | ---------------- | ------------------------- | ------------------------------------------------- | ---------------------------- |
48927
- | Build | \`mise run {{build-task}}\` | {{Build the experiment}} | {{Actual result or pending}} |
48928
- | Test | \`mise run {{test-task}}\` | {{Validate the experimental behavior}} | {{Actual result or pending}} |
48929
- | Run {{approach}} | \`mise run {{run-task}}\` | {{Run this prototype; repeat for every approach}} | {{Actual result or pending}} |
48996
+ | Task | Command | Purpose | Observed result |
48997
+ | --- | --- | --- | --- |
48998
+ | Build | \`mise run {{build-task}}\` | {{Build the experiment}} | {{Actual result or pending}} |
48999
+ | Test | \`mise run {{test-task}}\` | {{Validate the experimental behavior}} | {{Actual result or pending}} |
49000
+ | Run {{approach}} | \`mise run {{run-task}}\` | {{Run this prototype; repeat for every approach}} | {{Actual result or pending}} |
48930
49001
 
48931
49002
  {{Explain any genuinely inapplicable build/test task and its alternative validation. Identify unrun checks explicitly.}}
48932
49003
 
@@ -48979,15 +49050,77 @@ Project owners need to invite teammates without sharing credentials. Recipients
48979
49050
 
48980
49051
  ### Big Ideas
48981
49052
 
48982
- Store a single-use invitation tied to project, email, and expiry. Acceptance adds membership in the same transaction that consumes the invitation. Queue delivery after saving the invite so a temporary email failure cannot lose it.
49053
+ Keep invitation policy in the service and transport concerns in the handler. Store a single-use token hash, project, recipient, and expiry. Use the existing mail queue; no new service is needed.
49054
+
49055
+ \`\`\`mermaid
49056
+ flowchart LR
49057
+ HTTP["HTTP handler"] --> Service["Invitation service<br/>eligibility and acceptance"]
49058
+ Service --> Store["Invitation store<br/>atomic membership + consumption"]
49059
+ Service --> Queue["Existing mail queue"]
49060
+ \`\`\`
49061
+
49062
+ Acceptance adds membership and consumes the invitation in one transaction. Invalid acceptance leaves both unchanged. Queue delivery after saving, so an email outage cannot lose an invitation.
49063
+
49064
+ \`\`\`mermaid
49065
+ flowchart TD
49066
+ Accept["Recipient accepts"] --> Check{"Correct recipient,<br/>unused and unexpired?"}
49067
+ Check -->|"no"| Reject["Reject; leave state unchanged"]
49068
+ Check -->|"yes"| Save["Create membership + consume token<br/>in one transaction"]
49069
+ Save --> Result["Return membership"]
49070
+ \`\`\`
49071
+
49072
+ ### Infrastructure Changes
49073
+
49074
+ N/A.
48983
49075
 
48984
49076
  ### Interfaces
48985
49077
 
48986
- \`POST /projects/:id/invitations\` creates a pending invite; \`POST /invitations/:token/accept\` returns membership or a safe expired/used error. \`src/invitations/store.ts\` owns persistence; \`src/invitations/service.ts\` owns eligibility, expiry, and token consumption; \`src/http/invitations.ts\` maps results to responses.
49078
+ \`\`\`text
49079
+ POST /projects/:id/invitations -> pending invitation (owner only)
49080
+ POST /invitations/:token/accept -> membership or safe rejection
49081
+
49082
+ createInvitation(project, email) -> save token hash, then queue delivery
49083
+ acceptInvitation(token, recipient) -> validate, then atomically consume + join
49084
+ \`\`\`
49085
+
49086
+ Never log raw tokens. Apply the existing request rate limit to creation; do not disclose another recipient's account details.
48987
49087
 
48988
49088
  ### Consequences
48989
49089
 
48990
- Email delivery can fail after persistence, so queue retries and show a pending invitation. Rate-limit creation to avoid abuse. Resend and cancellation are deferred.
49090
+ #### Repo Changes
49091
+
49092
+ Illustrative paths: \`+\` new, \`~\` changed, \`*\` largest edits. No files are removed.
49093
+
49094
+ \`\`\`text
49095
+ src/
49096
+ \u251C\u2500\u2500 invitations/
49097
+ \u2502 \u251C\u2500\u2500 store.ts + persistence and transaction
49098
+ \u2502 \u251C\u2500\u2500 service.ts + * invitation policy
49099
+ \u2502 \u2514\u2500\u2500 service.spec.ts + public API with real test store
49100
+ \u2514\u2500\u2500 http/
49101
+ \u251C\u2500\u2500 routes.ts ~ register invitation handlers
49102
+ \u251C\u2500\u2500 invitations.ts + transport and authorization
49103
+ \u2514\u2500\u2500 invitations.spec.ts + application request scenarios
49104
+ \`\`\`
49105
+
49106
+ #### Testing
49107
+
49108
+ \`\`\`text
49109
+ end-to-end/
49110
+ \u2514\u2500\u2500 invitation acceptance
49111
+ \u251C\u2500\u2500 owner sends invite -> recipient accepts -> membership appears
49112
+ \u2514\u2500\u2500 wrong recipient, expired, or used token -> rejection; state unchanged
49113
+ integration/
49114
+ \u2514\u2500\u2500 invitation service
49115
+ \u251C\u2500\u2500 failed transaction -> membership and token changes roll back
49116
+ \u2514\u2500\u2500 email failure -> saved invitation stays pending for retry
49117
+ \`\`\`
49118
+
49119
+ Use request scenarios in \`src/http/invitations.spec.ts\` and service scenarios in \`src/invitations/service.spec.ts\`. Reuse fixtures with a real test store.
49120
+
49121
+ #### Trade-offs
49122
+
49123
+ Email delivery can fail after persistence. Use existing queue retries and keep the invitation pending. Resend, cancellation, and bulk invitations are deferred.
48991
49124
 
48992
49125
  ## Phases
48993
49126
 
@@ -48995,19 +49128,20 @@ Email delivery can fail after persistence, so queue retries and show a pending i
48995
49128
 
48996
49129
  - [ ] Add a unique token hash, recipient email, project ID, expiry, and consumed timestamp to invitation storage in \`src/invitations/store.ts\`; never log raw tokens.
48997
49130
  - [ ] Implement transactional membership creation and token consumption in \`src/invitations/service.ts\`; reject expired, used, or wrong-recipient tokens without a membership change.
48998
- - [ ] Add boundary, repeat-use, and rollback tests in \`src/invitations/service.test.ts\`.
49131
+ - [ ] Exercise \`createInvitation\` and \`acceptInvitation\` through the service public API with the real test store in \`src/invitations/service.spec.ts\`. Verify rejection leaves membership and token state unchanged, including a failed transaction.
48999
49132
  - [ ] Validation
49000
- - [ ] Run invitation unit tests, lint, and format checks.
49001
- - [ ] Commit this phase and confirm CI is green.
49133
+ - [ ] Run the service contract scenarios; use grouped fixtures and clear Arrange/Act/Assert blocks.
49134
+ - [ ] Run relevant lint and format checks.
49002
49135
 
49003
49136
  ### Phase 2: Expose and deliver invitations
49004
49137
 
49005
49138
  - [ ] Add owner creation and recipient acceptance handlers in \`src/http/invitations.ts\` with safe error responses and creation rate limits.
49006
49139
  - [ ] Queue delivery after persistence, retry transient mail failures, and expose the pending state to owners.
49007
- - [ ] Add endpoint integration tests for authorization, expiry, used tokens, and delivery failures in \`src/http/invitations.spec.ts\`.
49140
+ - [ ] In \`src/http/invitations.spec.ts\`, send an owner invitation, accept as its recipient, and read back membership. Verify wrong-recipient, expired, and used-token requests leave state unchanged. Verify an email failure keeps the saved invitation pending.
49008
49141
  - [ ] Validation
49009
- - [ ] Run unit and integration tests, lint, and format checks.
49010
- - [ ] Commit this phase and confirm CI is green.
49142
+ - [ ] Run the end-to-end acceptance/rejection scenarios, then the service integration scenarios listed above.
49143
+ - [ ] Run relevant lint and format checks. This example grants no commit or push permission.
49144
+ - [ ] If execution authorizes commits and expects CI, commit the changes and confirm CI is green for that commit.
49011
49145
 
49012
49146
  ## References
49013
49147
 
@@ -49038,17 +49172,77 @@ Readers should retain access to historical activity while the team changes stora
49038
49172
 
49039
49173
  Write to both stores temporarily, backfill older records with stable IDs, compare counts and ordering, then switch readers behind a reversible flag. Keep the legacy reader authoritative until the new store is verified.
49040
49174
 
49175
+ \`\`\`mermaid
49176
+ flowchart LR
49177
+ Writer["Activity writer"] --> Old["Legacy store"]
49178
+ Writer -->|"new dual write"| New["New store"]
49179
+ Old --> Backfill["Resumable backfill"] --> New
49180
+ Reader["Public history reader"] --> Flag{"Read flag"}
49181
+ Flag -->|"default / rollback"| Old
49182
+ Flag -->|"after parity check"| New
49183
+ \`\`\`
49184
+
49185
+ Stable IDs make retries safe. Track failed writes so comparison cannot silently treat missing data as a successful migration.
49186
+
49187
+ \`\`\`mermaid
49188
+ flowchart TD
49189
+ Provision["Provision store and credentials"] --> Dual["Enable dual writes"]
49190
+ Dual --> Backfill["Backfill in restartable batches"]
49191
+ Backfill --> Compare{"IDs, order,<br/>and cursors match?"}
49192
+ Compare -->|"no"| Repair["Report and repair; keep old reader"] --> Backfill
49193
+ Compare -->|"yes"| Switch["Switch read flag; keep dual writes"]
49194
+ Switch --> Rollback["Rollback: select old reader<br/>without losing new writes"]
49195
+ \`\`\`
49196
+
49041
49197
  ### Infrastructure Changes
49042
49198
 
49043
49199
  Provision the new activity store and credentials before dual writes. Retain the legacy store and its capacity until the rollback window closes; monitor write failures for each store separately.
49044
49200
 
49045
49201
  ### Interfaces
49046
49202
 
49047
- \`listActivity(projectId, cursor)\` preserves ordering and cursor behavior during cutover. \`src/activity/writer.ts\` owns dual writes, \`src/activity/backfill.ts\` copies historical records, and \`src/activity/reader.ts\` selects the store behind the feature flag.
49203
+ \`\`\`text
49204
+ writeActivity(event) -> stable ID; retry failed destination writes
49205
+ backfillActivity(projectId, cursor) -> checkpoint + discrepancy report
49206
+ listActivity(projectId, cursor) -> same records, order, and cursor contract
49207
+ readSource = legacy | new # reversible; legacy remains default
49208
+ \`\`\`
49048
49209
 
49049
49210
  ### Consequences
49050
49211
 
49051
- Dual writes increase latency and create a risk of partial failure. Record discrepancies and retry failed writes instead of hiding them. Removing the legacy store is outside this plan.
49212
+ #### Repo Changes
49213
+
49214
+ Illustrative paths: \`+\` new, \`~\` changed, \`*\` largest edits. No files are removed.
49215
+
49216
+ \`\`\`text
49217
+ infra/
49218
+ \u2514\u2500\u2500 activity-store.tf + store, credentials, write-failure metrics
49219
+ src/
49220
+ \u2514\u2500\u2500 activity/
49221
+ \u251C\u2500\u2500 writer.ts ~ * dual writes and retries
49222
+ \u251C\u2500\u2500 backfill.ts + * resumable batches and comparison
49223
+ \u251C\u2500\u2500 reader.ts ~ reversible read source
49224
+ \u251C\u2500\u2500 migration.spec.ts + public API parity and restart scenarios
49225
+ \u2514\u2500\u2500 history.spec.ts ~ application history across cutover/rollback
49226
+ \`\`\`
49227
+
49228
+ #### Testing
49229
+
49230
+ \`\`\`text
49231
+ end-to-end/
49232
+ \u2514\u2500\u2500 activity history
49233
+ \u251C\u2500\u2500 write and page history across cutover -> same records and cursors
49234
+ \u2514\u2500\u2500 write after cutover, then roll back -> no lost events
49235
+ integration/
49236
+ \u2514\u2500\u2500 activity storage
49237
+ \u251C\u2500\u2500 retry a failed destination -> one logical event in each store
49238
+ \u2514\u2500\u2500 interrupt and resume backfill -> same IDs and order; no duplicates
49239
+ \`\`\`
49240
+
49241
+ Use \`src/activity/history.spec.ts\` for application scenarios and \`src/activity/migration.spec.ts\` for public storage APIs with real test stores.
49242
+
49243
+ #### Trade-offs
49244
+
49245
+ Dual writes increase latency and allow partial failure. Record discrepancies and retry failed writes instead of hiding them. Removing the legacy store is outside this plan.
49052
49246
 
49053
49247
  ## Phases
49054
49248
 
@@ -49056,28 +49250,29 @@ Dual writes increase latency and create a risk of partial failure. Record discre
49056
49250
 
49057
49251
  - [ ] Provision the new store and credentials; add write-failure metrics for both stores.
49058
49252
  - [ ] Make \`src/activity/writer.ts\` write idempotently to both stores with stable activity IDs while retaining the legacy reader.
49059
- - [ ] Test partial-write retries and duplicate events in \`src/activity/writer.test.ts\`.
49253
+ - [ ] Exercise \`writeActivity\` against both test stores in \`src/activity/migration.spec.ts\`: retry a failed destination and verify one logical event in each store.
49060
49254
  - [ ] Validation
49061
- - [ ] Run writer tests, lint, and format checks; confirm write-failure metrics are visible.
49062
- - [ ] Commit this phase and confirm CI is green.
49255
+ - [ ] Run public writer contract scenarios; confirm write-failure metrics are visible.
49256
+ - [ ] Run relevant lint and format checks.
49063
49257
 
49064
49258
  ### Phase 2: Backfill and compare
49065
49259
 
49066
49260
  - [ ] Add resumable, idempotent batches in \`src/activity/backfill.ts\` without replacing newer dual-written records.
49067
49261
  - [ ] Compare record IDs, counts, and ordering per project; report mismatches before any reader switch.
49068
- - [ ] Add backfill restart and parity tests in \`src/activity/backfill.test.ts\`.
49262
+ - [ ] Extend \`migration.spec.ts\` through \`backfillActivity\` and \`listActivity\`: interrupt and resume a batch, then verify IDs, ordering, and cursors match without duplicate or lost events.
49069
49263
  - [ ] Validation
49070
- - [ ] Run backfill and writer tests, lint, and format checks; review mismatch reports.
49071
- - [ ] Commit this phase and confirm CI is green.
49264
+ - [ ] Run module public-API scenarios and review mismatch reports. Keep fixtures shared and Arrange/Act/Assert visible.
49265
+ - [ ] Run relevant lint and format checks.
49072
49266
 
49073
49267
  ### Phase 3: Switch reads with rollback
49074
49268
 
49075
49269
  - [ ] Route \`listActivity(projectId, cursor)\` through a reversible flag in \`src/activity/reader.ts\` and preserve cursor compatibility.
49076
- - [ ] Test new-store reads, cursor boundaries, and rollback to the legacy reader in \`src/activity/reader.spec.ts\`.
49270
+ - [ ] Extend application scenarios in \`src/activity/history.spec.ts\`: write and read paged history, switch stores, write again, and roll back. Assert the same visible history without lost events; do not assert private helper calls.
49077
49271
  - [ ] Keep dual writes and monitor parity through the rollback window.
49078
49272
  - [ ] Validation
49079
- - [ ] Run unit and integration tests, lint, and format checks; verify flag rollback.
49080
- - [ ] Commit this phase and confirm CI is green.
49273
+ - [ ] Run the end-to-end cutover/rollback scenarios, then the storage integration scenarios listed above.
49274
+ - [ ] Run relevant lint and format checks. This example grants no commit or push permission.
49275
+ - [ ] If execution authorizes commits and expects CI, commit the changes and confirm CI is green for that commit.
49081
49276
 
49082
49277
  ## References
49083
49278
 
@@ -49262,54 +49457,97 @@ Enable for a small cohort, compare completion and feedback, then expand or disab
49262
49457
  var init_prd_onboarding = () => {};
49263
49458
 
49264
49459
  // templates/examples/review-change.md
49265
- var review_change_default = `# Review: invitation expiry change
49460
+ var review_change_default = `# Review: invitation acceptance
49461
+
49462
+ - **Revision:** illustrative-change-17
49463
+ - **Intent:** Accept a valid invitation and reject an expired one without changing membership.
49464
+ - **Reviewer:** Authored example, not an executed review.
49266
49465
 
49267
- - **Revision:** example-change-17
49268
- - **Intent:** Reject expired invitations without creating membership.
49269
- - **Reviewer:** Example reviewer
49466
+ This is a fictional example. A real review must use its runtime model name in attribution.
49270
49467
 
49271
49468
  ## Intent Delivery
49272
49469
 
49273
- The acceptance path checks expiry before membership creation. The resend path was not part of this change.
49470
+ The change adds an acceptance path. Helper tests do not establish that the request saves membership or leaves it unchanged on rejection.
49274
49471
 
49275
49472
  ## Readability
49276
49473
 
49277
- Function names describe outcomes, and the request path is understandable without tracing helpers. Move the expiry guard before token decoding so a mid-level maintainer can see the rejection path at a glance.
49474
+ \`loadInvitation\` consumes the token and creates membership. I suggest \`acceptInvitation\` so callers can see the write.
49278
49475
 
49279
49476
  ## Semantic Organization
49280
49477
 
49281
- Invitation policy belongs in the invitation service, not the HTTP handler. The handler should own transport errors; the service should own token and membership rules.
49478
+ The handler maps HTTP to the service. The store owns persistence. No refactor is needed here.
49282
49479
 
49283
49480
  ## Maintainability
49284
49481
 
49285
- The shared expiry helper avoids duplicate rules in request and background paths. No new library or abstraction is needed for this change; formatting and static analysis should remain clean.
49482
+ The change follows the existing store pattern and reuses invitation status.
49286
49483
 
49287
49484
  ## Reliability/Testing
49288
49485
 
49289
- Tests cover expiry at the boundary and a used token. A clock-skew case is missing; add a readable test that checks the server clock source and confirms no membership is created on rejection.
49486
+ Helper tests do not prove that acceptance saves membership. Let's add application scenarios for acceptance and rejection, then read membership back.
49487
+
49488
+ Repeated setup and nested assertions hide the scenario in \`service.spec.ts:18\`. I suggest a fixture and separate blocks:
49489
+
49490
+ \`\`\`ts
49491
+ describe('acceptInvitation', () => {
49492
+ test('rejects an expired invitation without adding membership', async () => {
49493
+ // Arrange
49494
+ const { service, members, recipient, expiredToken } = await invitationFixture()
49495
+
49496
+ // Act
49497
+ const acceptance = service.acceptInvitation(expiredToken, recipient.id)
49498
+
49499
+ // Assert
49500
+ await expect(acceptance).rejects.toThrow(InvitationExpiredError)
49501
+ expect(await members.list(recipient.id)).toEqual([])
49502
+ })
49503
+ })
49504
+ \`\`\`
49505
+
49506
+ The fixture creates a real test store. I recommend keeping the action and assertions visible and reusing shared setup.
49507
+
49508
+ ## Repository Guidance
49509
+
49510
+ The fictional \`AGENTS.md\` requires Arrange/Act/Assert blocks and local helpers at the bottom. I suggest separating the mixed steps in \`service.spec.ts:18\`.
49290
49511
 
49291
49512
  ## Performance
49292
49513
 
49293
- One lookup occurs per acceptance; no new unbounded loop is present.
49514
+ SKIPPED: no concrete performance regression is shown and no deep performance audit was requested.
49294
49515
 
49295
49516
  ## Security
49296
49517
 
49297
- The response does not reveal whether an address has an account. The recipient is authorized before the token is consumed, tokens are treated as opaque values, and failed acceptance does not create membership.
49518
+ SKIPPED: no concrete security defect is shown; an exhaustive security audit is outside this example's scope.
49519
+
49520
+ ## Change Requests
49521
+
49522
+ ### src/invitations/service.ts:42 \u2014 Name the state-changing operation
49523
+
49524
+ - **Status:** open
49525
+ - **Request:** \`loadInvitation\` also writes membership. I suggest renaming it to \`acceptInvitation\` and updating callers.
49526
+
49527
+ > Generated via [actual model name from runtime]
49528
+
49529
+ ### src/invitations/service.spec.ts:18 \u2014 Expose the scenario and test its public result
49530
+
49531
+ - **Status:** open
49532
+ - **Request:** I suggest the fixture and Arrange/Act/Assert blocks above. Let's add acceptance/rejection scenarios that check saved membership, not just helpers.
49533
+
49534
+ > Generated via [actual model name from runtime]
49298
49535
 
49299
49536
  ## Findings
49300
49537
 
49301
- | Severity | Location | Evidence and impact | Required change or question |
49302
- | -------- | ----------------------- | ------------------------------------------------- | ----------------------------------------------------- |
49303
- | consider | \`src/invitations.ts:52\` | Clock skew near expiry may reject valid requests. | Add a boundary test and document server clock source. |
49538
+ | Severity | Location | Evidence and impact | Required change or question |
49539
+ | --- | --- | --- | --- |
49540
+ | consider | \`src/invitations/service.ts:42\` | A query-like name hides token consumption and membership creation. | I suggest renaming the operation and callers. |
49541
+ | consider | \`src/invitations/service.spec.ts:18\` | Repeated setup hides the action; helper coverage does not establish saved membership or rejection behavior. | I suggest clear blocks, a fixture, and a public/application scenario. |
49304
49542
 
49305
49543
  ## Overall Assessment
49306
49544
 
49307
- No blocking issue found in the reviewed path; address the boundary test before release.
49545
+ I recommend clearer API naming and acceptance/rejection scenarios. No private-helper coverage or speculative clock-skew request is needed.
49308
49546
 
49309
49547
  ## Verification and Limits
49310
49548
 
49311
- - **Checks run:** \`bun test src/invitations.test.ts\` \u2014 passed in this example.
49312
- - **Not verified:** Real email delivery and production clock behavior.
49549
+ - **Checks run:** none; this is an authored example, not a report of passing tests.
49550
+ - **Not verified:** execution, actual repository guidance, real email delivery, and production behavior.
49313
49551
  `;
49314
49552
  var init_review_change = () => {};
49315
49553
 
@@ -52086,7 +52324,7 @@ function createProgram(write = console.log, version = "0.1.0", options = {}) {
52086
52324
 
52087
52325
  // src/index.ts
52088
52326
  init_errors3();
52089
- var version2 = "0.8.1";
52327
+ var version2 = "0.9.0";
52090
52328
  try {
52091
52329
  await createProgram(console.log, version2).parseAsync(process.argv);
52092
52330
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@difflab/difflab-cli",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "private": false,
5
5
  "description": "A minimal Bun CLI starter built with Commander",
6
6
  "license": "Apache-2.0",