@difflab/difflab-cli 0.8.1 → 0.10.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 +9 -1
  2. package/dist/index.js +387 -107
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -66,12 +66,20 @@ difflab-cli templates list
66
66
  difflab-cli templates scaffold spec-driven-plan docs/plans first-plan.md --cwd /absolute/project/path
67
67
  ```
68
68
 
69
- The CLI defaults `--cwd` to its current directory. The MCP server exposes `template_list({})` and `scaffold({"name":"spec-driven-plan","cwd":"/absolute/project/path","path":"docs/plans","filename":"first-plan.md"})`. Both interfaces take a hyphenated template name, a relative destination **directory** (`path`, or `.` for `cwd`), and a separate **filename**. MCP always requires an absolute existing `cwd`. Missing destination directories are created. Available names: `spec-driven-plan`, `software-architecture-design`, `architecture-decision-record`, `product-requirements-document`, `code-review`, `planning-intent`, `ui-component-architecture`, `pull-request-description` (adapted from Diffpi's PR template), `flow-definition`, `flow-instance`, `exploration-summary`, `exploration-research`, `exploration-proposal`, and `poc-readme`.
69
+ The CLI defaults `--cwd` to its current directory. The MCP server exposes `template_list({})` and `scaffold({"name":"spec-driven-plan","cwd":"/absolute/project/path","path":"docs/plans","filename":"first-plan.md"})`. Both interfaces take a hyphenated template name, a relative destination **directory** (`path`, or `.` for `cwd`), and a separate **filename**. MCP always requires an absolute existing `cwd`. Missing destination directories are created. Available names: `spec-driven-plan`, `software-architecture-design`, `architecture-decision-record`, `product-requirements-document`, `agents`, `code-review`, `planning-intent`, `ui-component-architecture`, `pull-request-description` (adapted from Diffpi's PR template), `flow-definition`, `flow-instance`, `exploration-summary`, `exploration-research`, `exploration-proposal`, and `poc-readme`.
70
+
71
+ Use `difflab-cli templates scaffold agents . AGENTS.md --cwd /absolute/project/path` to start repository guidance. Fill the prompts with known rules and remove unused prompts. The template separates Hard Rules, Organization, Style, and Testing. Hard Rules name required task runners and repository tools. Organization separates directory layout from section headers, code ordering, and grouping within files. Review addressing saves explicit user repository rules immediately and marks inferred or uncertain rules with `[Proposed]`. It asks before reorganizing existing guidance to match the template and adapts the existing structure if permission is declined. It retains terse file-level organization rules and keeps guidance within 500 soft-wrapped source lines.
70
72
 
71
73
  On first use, missing bundled templates and filled examples are copied to `~/.difflab/templates` and `~/.difflab/templates/examples`. Edit the installed templates to customize future documents. Later runs add missing files but never replace existing ones, even after an upgrade. Examples are for reference, not selectable templates. Copying is literal: prompts and placeholders are not substituted. An unknown name, missing or invalid source, unsafe path, symlink, or existing destination causes an error; destination files are never overwritten. The target directory must remain inside `cwd` and the filename must be a single file name without separators. The one symlink exception is an initialized repository's `.difflab` link to its own user project store: `templates scaffold spec-driven-plan .difflab/plans/YYMMDD-name PLAN.md --cwd /absolute/repo` works in Git worktrees as well. To restore a bundled default, move your installed copy aside before listing again.
72
74
 
73
75
  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
76
 
77
+ 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.
78
+
79
+ 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.
80
+
81
+ 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.
82
+
75
83
  ## Flow definitions and runs
76
84
 
77
85
  `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
 
@@ -48551,6 +48618,45 @@ var product_requirements_document_default = `# [User outcome or product change]
48551
48618
  `;
48552
48619
  var init_product_requirements_document = () => {};
48553
48620
 
48621
+ // templates/agents.md
48622
+ var agents_default = `# Repository Guidance
48623
+
48624
+ <!-- Fill the prompts with known repository rules and remove unused prompts. Write terse, imperative bullets in simple English. Use soft wrapping and keep the whole file at or below 500 source lines, including headings and blank lines. Replace contradictory rules, merge duplicates, and prune less important guidance when adding rules. Prefix inferred or uncertain rules with [Proposed]; omit the prefix only for rules the user explicitly asks to add or says must always apply in this repo. Link to maintained docs for details; omit architecture explanations, README repetition, and code snippets. -->
48625
+
48626
+ ## Hard Rules
48627
+
48628
+ - Prefer the repository's existing task-runner tasks. See [task definitions](path-to-task-definitions).
48629
+ - [State required scaffolding and repository management tools. Link to their configuration.]
48630
+ - [Add other essential repository constraints.]
48631
+
48632
+ ## Organization
48633
+
48634
+ ### Repository layout
48635
+
48636
+ - [State directory layout and file naming conventions. Link to maintained docs for details.]
48637
+
48638
+ ### File layout
48639
+
48640
+ - [State prescribed section headers, code ordering, and grouping within each file type.]
48641
+ - [Retain terse rules for import, type, API, hook, and helper placement.]
48642
+
48643
+ ## Style
48644
+
48645
+ ### Languages
48646
+
48647
+ - [Replace this heading with each language used here and add its rules.]
48648
+
48649
+ ### Frameworks
48650
+
48651
+ - [Replace this heading with each framework that needs specific rules.]
48652
+
48653
+ ## Testing
48654
+
48655
+ - [State where unit, integration, and end-to-end tests belong.]
48656
+ - [State test naming, structure, shared helper, and validation rules. Link to task definitions.]
48657
+ `;
48658
+ var init_agents = () => {};
48659
+
48554
48660
  // templates/code-review.md
48555
48661
  var code_review_default = `# Review: [change or pull request]
48556
48662
 
@@ -48564,31 +48670,35 @@ var code_review_default = `# Review: [change or pull request]
48564
48670
 
48565
48671
  ## Readability
48566
48672
 
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?]
48673
+ [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
48674
 
48569
48675
  ## Semantic Organization
48570
48676
 
48571
- [Are responsibilities, boundaries, and public APIs arranged by meaning? Are modules semantically meaningful? Is code in applications and libraries well sliced by concerns?]
48677
+ [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
48678
 
48573
48679
  ## Maintainability
48574
48680
 
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?]
48681
+ [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
48682
 
48577
48683
  ## Reliability/Testing
48578
48684
 
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.]
48685
+ [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.]
48686
+
48687
+ ## Repository Guidance
48688
+
48689
+ [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
48690
 
48581
48691
  ## Performance
48582
48692
 
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.]
48693
+ [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
48694
 
48585
48695
  ## Security
48586
48696
 
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.]
48697
+ [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
48698
 
48589
48699
  ## Change Requests
48590
48700
 
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.
48701
+ 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
48702
 
48593
48703
  ### [file:line \u2014 request]
48594
48704
 
@@ -48603,13 +48713,13 @@ Track actionable inline requests with one entry per request. End each generated
48603
48713
 
48604
48714
  ## Findings
48605
48715
 
48606
- | Severity | Location | Evidence and impact | Required change or question |
48607
- | ------------------------ | ----------- | -------------------------- | --------------------------- |
48608
- | [blocking/consider/note] | [file:line] | [reproducible observation] | [concrete action] |
48716
+ | Severity | Location | Evidence and impact | Required change or question |
48717
+ | --- | --- | --- | --- |
48718
+ | [blocking/consider/note] | [file:line] | [reproducible observation] | [concrete action] |
48609
48719
 
48610
48720
  ## Overall Assessment
48611
48721
 
48612
- [Recommendation and reasons; distinguish findings from preferences.]
48722
+ [Recommendation and reasons; distinguish material findings from preferences. A clean review may have no change requests. Keep section assessments concise.]
48613
48723
 
48614
48724
  ## Verification and Limits
48615
48725
 
@@ -48726,11 +48836,11 @@ var flow_definition_default = `# {{name}}
48726
48836
 
48727
48837
  ## Flags
48728
48838
 
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. |
48839
+ | Flag | Default | Capability | Effect |
48840
+ | --- | --- | --- | --- |
48841
+ | --local | false | local | Omit steps with \`unless: --local\`; never forward to every skill. |
48842
+ | --commit | false | local-commit | Permit only explicitly commit-capable selected steps to make scoped local commits. |
48843
+ | --remote-review | false | remote-review | Opt in to remote review with independent forge authorization. |
48734
48844
 
48735
48845
  ## Steps
48736
48846
 
@@ -48922,11 +49032,11 @@ base_commit: '{{full SHA of origin/main at branch creation}}'
48922
49032
 
48923
49033
  {{Tool versions, dependency installation, environment inputs, and required services.}}
48924
49034
 
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}} |
49035
+ | Task | Command | Purpose | Observed result |
49036
+ | --- | --- | --- | --- |
49037
+ | Build | \`mise run {{build-task}}\` | {{Build the experiment}} | {{Actual result or pending}} |
49038
+ | Test | \`mise run {{test-task}}\` | {{Validate the experimental behavior}} | {{Actual result or pending}} |
49039
+ | Run {{approach}} | \`mise run {{run-task}}\` | {{Run this prototype; repeat for every approach}} | {{Actual result or pending}} |
48930
49040
 
48931
49041
  {{Explain any genuinely inapplicable build/test task and its alternative validation. Identify unrun checks explicitly.}}
48932
49042
 
@@ -48979,15 +49089,77 @@ Project owners need to invite teammates without sharing credentials. Recipients
48979
49089
 
48980
49090
  ### Big Ideas
48981
49091
 
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.
49092
+ 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.
49093
+
49094
+ \`\`\`mermaid
49095
+ flowchart LR
49096
+ HTTP["HTTP handler"] --> Service["Invitation service<br/>eligibility and acceptance"]
49097
+ Service --> Store["Invitation store<br/>atomic membership + consumption"]
49098
+ Service --> Queue["Existing mail queue"]
49099
+ \`\`\`
49100
+
49101
+ 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.
49102
+
49103
+ \`\`\`mermaid
49104
+ flowchart TD
49105
+ Accept["Recipient accepts"] --> Check{"Correct recipient,<br/>unused and unexpired?"}
49106
+ Check -->|"no"| Reject["Reject; leave state unchanged"]
49107
+ Check -->|"yes"| Save["Create membership + consume token<br/>in one transaction"]
49108
+ Save --> Result["Return membership"]
49109
+ \`\`\`
49110
+
49111
+ ### Infrastructure Changes
49112
+
49113
+ N/A.
48983
49114
 
48984
49115
  ### Interfaces
48985
49116
 
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.
49117
+ \`\`\`text
49118
+ POST /projects/:id/invitations -> pending invitation (owner only)
49119
+ POST /invitations/:token/accept -> membership or safe rejection
49120
+
49121
+ createInvitation(project, email) -> save token hash, then queue delivery
49122
+ acceptInvitation(token, recipient) -> validate, then atomically consume + join
49123
+ \`\`\`
49124
+
49125
+ Never log raw tokens. Apply the existing request rate limit to creation; do not disclose another recipient's account details.
48987
49126
 
48988
49127
  ### Consequences
48989
49128
 
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.
49129
+ #### Repo Changes
49130
+
49131
+ Illustrative paths: \`+\` new, \`~\` changed, \`*\` largest edits. No files are removed.
49132
+
49133
+ \`\`\`text
49134
+ src/
49135
+ \u251C\u2500\u2500 invitations/
49136
+ \u2502 \u251C\u2500\u2500 store.ts + persistence and transaction
49137
+ \u2502 \u251C\u2500\u2500 service.ts + * invitation policy
49138
+ \u2502 \u2514\u2500\u2500 service.spec.ts + public API with real test store
49139
+ \u2514\u2500\u2500 http/
49140
+ \u251C\u2500\u2500 routes.ts ~ register invitation handlers
49141
+ \u251C\u2500\u2500 invitations.ts + transport and authorization
49142
+ \u2514\u2500\u2500 invitations.spec.ts + application request scenarios
49143
+ \`\`\`
49144
+
49145
+ #### Testing
49146
+
49147
+ \`\`\`text
49148
+ end-to-end/
49149
+ \u2514\u2500\u2500 invitation acceptance
49150
+ \u251C\u2500\u2500 owner sends invite -> recipient accepts -> membership appears
49151
+ \u2514\u2500\u2500 wrong recipient, expired, or used token -> rejection; state unchanged
49152
+ integration/
49153
+ \u2514\u2500\u2500 invitation service
49154
+ \u251C\u2500\u2500 failed transaction -> membership and token changes roll back
49155
+ \u2514\u2500\u2500 email failure -> saved invitation stays pending for retry
49156
+ \`\`\`
49157
+
49158
+ 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.
49159
+
49160
+ #### Trade-offs
49161
+
49162
+ Email delivery can fail after persistence. Use existing queue retries and keep the invitation pending. Resend, cancellation, and bulk invitations are deferred.
48991
49163
 
48992
49164
  ## Phases
48993
49165
 
@@ -48995,19 +49167,20 @@ Email delivery can fail after persistence, so queue retries and show a pending i
48995
49167
 
48996
49168
  - [ ] 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
49169
  - [ ] 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\`.
49170
+ - [ ] 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
49171
  - [ ] Validation
49000
- - [ ] Run invitation unit tests, lint, and format checks.
49001
- - [ ] Commit this phase and confirm CI is green.
49172
+ - [ ] Run the service contract scenarios; use grouped fixtures and clear Arrange/Act/Assert blocks.
49173
+ - [ ] Run relevant lint and format checks.
49002
49174
 
49003
49175
  ### Phase 2: Expose and deliver invitations
49004
49176
 
49005
49177
  - [ ] Add owner creation and recipient acceptance handlers in \`src/http/invitations.ts\` with safe error responses and creation rate limits.
49006
49178
  - [ ] 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\`.
49179
+ - [ ] 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
49180
  - [ ] Validation
49009
- - [ ] Run unit and integration tests, lint, and format checks.
49010
- - [ ] Commit this phase and confirm CI is green.
49181
+ - [ ] Run the end-to-end acceptance/rejection scenarios, then the service integration scenarios listed above.
49182
+ - [ ] Run relevant lint and format checks. This example grants no commit or push permission.
49183
+ - [ ] If execution authorizes commits and expects CI, commit the changes and confirm CI is green for that commit.
49011
49184
 
49012
49185
  ## References
49013
49186
 
@@ -49038,17 +49211,77 @@ Readers should retain access to historical activity while the team changes stora
49038
49211
 
49039
49212
  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
49213
 
49214
+ \`\`\`mermaid
49215
+ flowchart LR
49216
+ Writer["Activity writer"] --> Old["Legacy store"]
49217
+ Writer -->|"new dual write"| New["New store"]
49218
+ Old --> Backfill["Resumable backfill"] --> New
49219
+ Reader["Public history reader"] --> Flag{"Read flag"}
49220
+ Flag -->|"default / rollback"| Old
49221
+ Flag -->|"after parity check"| New
49222
+ \`\`\`
49223
+
49224
+ Stable IDs make retries safe. Track failed writes so comparison cannot silently treat missing data as a successful migration.
49225
+
49226
+ \`\`\`mermaid
49227
+ flowchart TD
49228
+ Provision["Provision store and credentials"] --> Dual["Enable dual writes"]
49229
+ Dual --> Backfill["Backfill in restartable batches"]
49230
+ Backfill --> Compare{"IDs, order,<br/>and cursors match?"}
49231
+ Compare -->|"no"| Repair["Report and repair; keep old reader"] --> Backfill
49232
+ Compare -->|"yes"| Switch["Switch read flag; keep dual writes"]
49233
+ Switch --> Rollback["Rollback: select old reader<br/>without losing new writes"]
49234
+ \`\`\`
49235
+
49041
49236
  ### Infrastructure Changes
49042
49237
 
49043
49238
  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
49239
 
49045
49240
  ### Interfaces
49046
49241
 
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.
49242
+ \`\`\`text
49243
+ writeActivity(event) -> stable ID; retry failed destination writes
49244
+ backfillActivity(projectId, cursor) -> checkpoint + discrepancy report
49245
+ listActivity(projectId, cursor) -> same records, order, and cursor contract
49246
+ readSource = legacy | new # reversible; legacy remains default
49247
+ \`\`\`
49048
49248
 
49049
49249
  ### Consequences
49050
49250
 
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.
49251
+ #### Repo Changes
49252
+
49253
+ Illustrative paths: \`+\` new, \`~\` changed, \`*\` largest edits. No files are removed.
49254
+
49255
+ \`\`\`text
49256
+ infra/
49257
+ \u2514\u2500\u2500 activity-store.tf + store, credentials, write-failure metrics
49258
+ src/
49259
+ \u2514\u2500\u2500 activity/
49260
+ \u251C\u2500\u2500 writer.ts ~ * dual writes and retries
49261
+ \u251C\u2500\u2500 backfill.ts + * resumable batches and comparison
49262
+ \u251C\u2500\u2500 reader.ts ~ reversible read source
49263
+ \u251C\u2500\u2500 migration.spec.ts + public API parity and restart scenarios
49264
+ \u2514\u2500\u2500 history.spec.ts ~ application history across cutover/rollback
49265
+ \`\`\`
49266
+
49267
+ #### Testing
49268
+
49269
+ \`\`\`text
49270
+ end-to-end/
49271
+ \u2514\u2500\u2500 activity history
49272
+ \u251C\u2500\u2500 write and page history across cutover -> same records and cursors
49273
+ \u2514\u2500\u2500 write after cutover, then roll back -> no lost events
49274
+ integration/
49275
+ \u2514\u2500\u2500 activity storage
49276
+ \u251C\u2500\u2500 retry a failed destination -> one logical event in each store
49277
+ \u2514\u2500\u2500 interrupt and resume backfill -> same IDs and order; no duplicates
49278
+ \`\`\`
49279
+
49280
+ Use \`src/activity/history.spec.ts\` for application scenarios and \`src/activity/migration.spec.ts\` for public storage APIs with real test stores.
49281
+
49282
+ #### Trade-offs
49283
+
49284
+ 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
49285
 
49053
49286
  ## Phases
49054
49287
 
@@ -49056,28 +49289,29 @@ Dual writes increase latency and create a risk of partial failure. Record discre
49056
49289
 
49057
49290
  - [ ] Provision the new store and credentials; add write-failure metrics for both stores.
49058
49291
  - [ ] 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\`.
49292
+ - [ ] 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
49293
  - [ ] Validation
49061
- - [ ] Run writer tests, lint, and format checks; confirm write-failure metrics are visible.
49062
- - [ ] Commit this phase and confirm CI is green.
49294
+ - [ ] Run public writer contract scenarios; confirm write-failure metrics are visible.
49295
+ - [ ] Run relevant lint and format checks.
49063
49296
 
49064
49297
  ### Phase 2: Backfill and compare
49065
49298
 
49066
49299
  - [ ] Add resumable, idempotent batches in \`src/activity/backfill.ts\` without replacing newer dual-written records.
49067
49300
  - [ ] 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\`.
49301
+ - [ ] 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
49302
  - [ ] Validation
49070
- - [ ] Run backfill and writer tests, lint, and format checks; review mismatch reports.
49071
- - [ ] Commit this phase and confirm CI is green.
49303
+ - [ ] Run module public-API scenarios and review mismatch reports. Keep fixtures shared and Arrange/Act/Assert visible.
49304
+ - [ ] Run relevant lint and format checks.
49072
49305
 
49073
49306
  ### Phase 3: Switch reads with rollback
49074
49307
 
49075
49308
  - [ ] 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\`.
49309
+ - [ ] 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
49310
  - [ ] Keep dual writes and monitor parity through the rollback window.
49078
49311
  - [ ] Validation
49079
- - [ ] Run unit and integration tests, lint, and format checks; verify flag rollback.
49080
- - [ ] Commit this phase and confirm CI is green.
49312
+ - [ ] Run the end-to-end cutover/rollback scenarios, then the storage integration scenarios listed above.
49313
+ - [ ] Run relevant lint and format checks. This example grants no commit or push permission.
49314
+ - [ ] If execution authorizes commits and expects CI, commit the changes and confirm CI is green for that commit.
49081
49315
 
49082
49316
  ## References
49083
49317
 
@@ -49262,54 +49496,97 @@ Enable for a small cohort, compare completion and feedback, then expand or disab
49262
49496
  var init_prd_onboarding = () => {};
49263
49497
 
49264
49498
  // templates/examples/review-change.md
49265
- var review_change_default = `# Review: invitation expiry change
49499
+ var review_change_default = `# Review: invitation acceptance
49500
+
49501
+ - **Revision:** illustrative-change-17
49502
+ - **Intent:** Accept a valid invitation and reject an expired one without changing membership.
49503
+ - **Reviewer:** Authored example, not an executed review.
49266
49504
 
49267
- - **Revision:** example-change-17
49268
- - **Intent:** Reject expired invitations without creating membership.
49269
- - **Reviewer:** Example reviewer
49505
+ This is a fictional example. A real review must use its runtime model name in attribution.
49270
49506
 
49271
49507
  ## Intent Delivery
49272
49508
 
49273
- The acceptance path checks expiry before membership creation. The resend path was not part of this change.
49509
+ The change adds an acceptance path. Helper tests do not establish that the request saves membership or leaves it unchanged on rejection.
49274
49510
 
49275
49511
  ## Readability
49276
49512
 
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.
49513
+ \`loadInvitation\` consumes the token and creates membership. I suggest \`acceptInvitation\` so callers can see the write.
49278
49514
 
49279
49515
  ## Semantic Organization
49280
49516
 
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.
49517
+ The handler maps HTTP to the service. The store owns persistence. No refactor is needed here.
49282
49518
 
49283
49519
  ## Maintainability
49284
49520
 
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.
49521
+ The change follows the existing store pattern and reuses invitation status.
49286
49522
 
49287
49523
  ## Reliability/Testing
49288
49524
 
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.
49525
+ Helper tests do not prove that acceptance saves membership. Let's add application scenarios for acceptance and rejection, then read membership back.
49526
+
49527
+ Repeated setup and nested assertions hide the scenario in \`service.spec.ts:18\`. I suggest a fixture and separate blocks:
49528
+
49529
+ \`\`\`ts
49530
+ describe('acceptInvitation', () => {
49531
+ test('rejects an expired invitation without adding membership', async () => {
49532
+ // Arrange
49533
+ const { service, members, recipient, expiredToken } = await invitationFixture()
49534
+
49535
+ // Act
49536
+ const acceptance = service.acceptInvitation(expiredToken, recipient.id)
49537
+
49538
+ // Assert
49539
+ await expect(acceptance).rejects.toThrow(InvitationExpiredError)
49540
+ expect(await members.list(recipient.id)).toEqual([])
49541
+ })
49542
+ })
49543
+ \`\`\`
49544
+
49545
+ The fixture creates a real test store. I recommend keeping the action and assertions visible and reusing shared setup.
49546
+
49547
+ ## Repository Guidance
49548
+
49549
+ 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
49550
 
49291
49551
  ## Performance
49292
49552
 
49293
- One lookup occurs per acceptance; no new unbounded loop is present.
49553
+ SKIPPED: no concrete performance regression is shown and no deep performance audit was requested.
49294
49554
 
49295
49555
  ## Security
49296
49556
 
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.
49557
+ SKIPPED: no concrete security defect is shown; an exhaustive security audit is outside this example's scope.
49558
+
49559
+ ## Change Requests
49560
+
49561
+ ### src/invitations/service.ts:42 \u2014 Name the state-changing operation
49562
+
49563
+ - **Status:** open
49564
+ - **Request:** \`loadInvitation\` also writes membership. I suggest renaming it to \`acceptInvitation\` and updating callers.
49565
+
49566
+ > Generated via [actual model name from runtime]
49567
+
49568
+ ### src/invitations/service.spec.ts:18 \u2014 Expose the scenario and test its public result
49569
+
49570
+ - **Status:** open
49571
+ - **Request:** I suggest the fixture and Arrange/Act/Assert blocks above. Let's add acceptance/rejection scenarios that check saved membership, not just helpers.
49572
+
49573
+ > Generated via [actual model name from runtime]
49298
49574
 
49299
49575
  ## Findings
49300
49576
 
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. |
49577
+ | Severity | Location | Evidence and impact | Required change or question |
49578
+ | --- | --- | --- | --- |
49579
+ | consider | \`src/invitations/service.ts:42\` | A query-like name hides token consumption and membership creation. | I suggest renaming the operation and callers. |
49580
+ | 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
49581
 
49305
49582
  ## Overall Assessment
49306
49583
 
49307
- No blocking issue found in the reviewed path; address the boundary test before release.
49584
+ I recommend clearer API naming and acceptance/rejection scenarios. No private-helper coverage or speculative clock-skew request is needed.
49308
49585
 
49309
49586
  ## Verification and Limits
49310
49587
 
49311
- - **Checks run:** \`bun test src/invitations.test.ts\` \u2014 passed in this example.
49312
- - **Not verified:** Real email delivery and production clock behavior.
49588
+ - **Checks run:** none; this is an authored example, not a report of passing tests.
49589
+ - **Not verified:** execution, actual repository guidance, real email delivery, and production behavior.
49313
49590
  `;
49314
49591
  var init_review_change = () => {};
49315
49592
 
@@ -49463,12 +49740,13 @@ Add invitation resend and cancellation in a separate change.
49463
49740
  var init_pull_request_feature = () => {};
49464
49741
 
49465
49742
  // src/templates/source.ts
49466
- var catalog, contents, embeddedSource;
49743
+ var contents, catalog, embeddedSource;
49467
49744
  var init_source = __esm(() => {
49468
49745
  init_spec_driven_plan();
49469
49746
  init_software_architecture_design();
49470
49747
  init_architecture_decision_record();
49471
49748
  init_product_requirements_document();
49749
+ init_agents();
49472
49750
  init_code_review();
49473
49751
  init_planning_intent();
49474
49752
  init_ui_component_architecture();
@@ -49489,27 +49767,12 @@ var init_source = __esm(() => {
49489
49767
  init_component_dialog();
49490
49768
  init_component_navigation();
49491
49769
  init_pull_request_feature();
49492
- catalog = [
49493
- { name: "spec-driven-plan", description: "Spec-driven implementation plan" },
49494
- { name: "software-architecture-design", description: "Software architecture design" },
49495
- { name: "architecture-decision-record", description: "Architecture decision record" },
49496
- { name: "product-requirements-document", description: "Product requirements document" },
49497
- { name: "code-review", description: "Thorough code review" },
49498
- { name: "planning-intent", description: "Planning intent for a user to fill in" },
49499
- { name: "ui-component-architecture", description: "UI component design and implementation" },
49500
- { name: "pull-request-description", description: "Draft pull request description" },
49501
- { name: "flow-definition", description: "Reusable global Agent Skill flow definition" },
49502
- { name: "flow-instance", description: "Frozen repository-local flow run" },
49503
- { name: "exploration-summary", description: "Source-grounded technical exploration summary" },
49504
- { name: "exploration-research", description: "Technical exploration research notes" },
49505
- { name: "exploration-proposal", description: "Optional technical exploration approach" },
49506
- { name: "poc-readme", description: "Disposable proof-of-concept branch README" }
49507
- ];
49508
49770
  contents = {
49509
49771
  "spec-driven-plan.md": spec_driven_plan_default,
49510
49772
  "software-architecture-design.md": software_architecture_design_default,
49511
49773
  "architecture-decision-record.md": architecture_decision_record_default,
49512
49774
  "product-requirements-document.md": product_requirements_document_default,
49775
+ "agents.md": agents_default,
49513
49776
  "code-review.md": code_review_default,
49514
49777
  "planning-intent.md": planning_intent_default,
49515
49778
  "ui-component-architecture.md": ui_component_architecture_default,
@@ -49531,6 +49794,23 @@ var init_source = __esm(() => {
49531
49794
  "examples/component-navigation.md": component_navigation_default,
49532
49795
  "examples/pull-request-feature.md": pull_request_feature_default
49533
49796
  };
49797
+ catalog = [
49798
+ { name: "spec-driven-plan", description: "Spec-driven implementation plan" },
49799
+ { name: "software-architecture-design", description: "Software architecture design" },
49800
+ { name: "architecture-decision-record", description: "Architecture decision record" },
49801
+ { name: "product-requirements-document", description: "Product requirements document" },
49802
+ { name: "agents", description: "Concise repository rules for AGENTS.md" },
49803
+ { name: "code-review", description: "Thorough code review" },
49804
+ { name: "planning-intent", description: "Planning intent for a user to fill in" },
49805
+ { name: "ui-component-architecture", description: "UI component design and implementation" },
49806
+ { name: "pull-request-description", description: "Draft pull request description" },
49807
+ { name: "flow-definition", description: "Reusable global Agent Skill flow definition" },
49808
+ { name: "flow-instance", description: "Frozen repository-local flow run" },
49809
+ { name: "exploration-summary", description: "Source-grounded technical exploration summary" },
49810
+ { name: "exploration-research", description: "Technical exploration research notes" },
49811
+ { name: "exploration-proposal", description: "Optional technical exploration approach" },
49812
+ { name: "poc-readme", description: "Disposable proof-of-concept branch README" }
49813
+ ];
49534
49814
  embeddedSource = {
49535
49815
  catalog,
49536
49816
  assets: Object.keys(contents),
@@ -49896,7 +50176,7 @@ function agentAdapter(name) {
49896
50176
  return Object.hasOwn(agentAdapters, name) ? agentAdapters[name] : undefined;
49897
50177
  }
49898
50178
  var agentAdapters, agentNames;
49899
- var init_agents = __esm(() => {
50179
+ var init_agents2 = __esm(() => {
49900
50180
  init_adapter();
49901
50181
  init_claude_code();
49902
50182
  init_codex();
@@ -49939,7 +50219,7 @@ async function setupMcpClients(clients, write) {
49939
50219
  write("Restart or reload the selected client to use the Difflab MCP tools.");
49940
50220
  }
49941
50221
  var init_setup = __esm(() => {
49942
- init_agents();
50222
+ init_agents2();
49943
50223
  });
49944
50224
 
49945
50225
  // src/mcp/index.ts
@@ -52086,7 +52366,7 @@ function createProgram(write = console.log, version = "0.1.0", options = {}) {
52086
52366
 
52087
52367
  // src/index.ts
52088
52368
  init_errors3();
52089
- var version2 = "0.8.1";
52369
+ var version2 = "0.10.0";
52090
52370
  try {
52091
52371
  await createProgram(console.log, version2).parseAsync(process.argv);
52092
52372
  } 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.10.0",
4
4
  "private": false,
5
5
  "description": "A minimal Bun CLI starter built with Commander",
6
6
  "license": "Apache-2.0",