@difflab/difflab-cli 0.8.0 → 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.
- package/README.md +7 -1
- package/dist/index.js +337 -83
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Initialization requires a GitHub HTTPS or SSH origin and never creates a Git rep
|
|
|
31
31
|
|
|
32
32
|
If initialization is interrupted, retry `difflab init <PROJECT_KEY>`; matching partial links and database rows can be reused, but conflicting paths require manual inspection. Do not delete the shared database to repair a repository link.
|
|
33
33
|
|
|
34
|
-
The `project_context({ cwd })` MCP tool inspects initialized repositories without creating files or applying migrations.
|
|
34
|
+
The `project_context({ cwd })` MCP tool inspects initialized repositories without creating files or applying migrations. Existing `todo_*` MCP tools remain available.
|
|
35
35
|
|
|
36
36
|
## MCP setup
|
|
37
37
|
|
|
@@ -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
|
-
|
|
37772
|
-
|
|
37773
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
38043
|
-
|
|
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 ||
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
|
|
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
|
-
[
|
|
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
|
-
- [ ]
|
|
48337
|
-
- [ ]
|
|
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
|
-
- [ ]
|
|
48347
|
-
- [ ]
|
|
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
|
-
- [ ]
|
|
48357
|
-
- [ ]
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
|
|
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
|
|
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
|
|
48730
|
-
|
|
|
48731
|
-
| --local
|
|
48732
|
-
| --commit
|
|
48733
|
-
| --remote-review | false
|
|
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
|
|
|
@@ -48918,6 +48989,22 @@ base_commit: '{{full SHA of origin/main at branch creation}}'
|
|
|
48918
48989
|
|
|
48919
48990
|
{{Question to answer and the smallest useful success criterion.}}
|
|
48920
48991
|
|
|
48992
|
+
## Setup and tasks
|
|
48993
|
+
|
|
48994
|
+
{{Tool versions, dependency installation, environment inputs, and required services.}}
|
|
48995
|
+
|
|
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}} |
|
|
49001
|
+
|
|
49002
|
+
{{Explain any genuinely inapplicable build/test task and its alternative validation. Identify unrun checks explicitly.}}
|
|
49003
|
+
|
|
49004
|
+
## Code map
|
|
49005
|
+
|
|
49006
|
+
{{Focused submodules, their responsibilities and small public APIs, entrypoints, and minimal examples. Keep the experiment direct and avoid speculative abstractions or giant implementation files.}}
|
|
49007
|
+
|
|
48921
49008
|
## Alternatives
|
|
48922
49009
|
|
|
48923
49010
|
- {{Alternative or variation to compare, if any.}}
|
|
@@ -48963,15 +49050,77 @@ Project owners need to invite teammates without sharing credentials. Recipients
|
|
|
48963
49050
|
|
|
48964
49051
|
### Big Ideas
|
|
48965
49052
|
|
|
48966
|
-
|
|
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.
|
|
48967
49075
|
|
|
48968
49076
|
### Interfaces
|
|
48969
49077
|
|
|
48970
|
-
|
|
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.
|
|
48971
49087
|
|
|
48972
49088
|
### Consequences
|
|
48973
49089
|
|
|
48974
|
-
|
|
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.
|
|
48975
49124
|
|
|
48976
49125
|
## Phases
|
|
48977
49126
|
|
|
@@ -48979,19 +49128,20 @@ Email delivery can fail after persistence, so queue retries and show a pending i
|
|
|
48979
49128
|
|
|
48980
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.
|
|
48981
49130
|
- [ ] Implement transactional membership creation and token consumption in \`src/invitations/service.ts\`; reject expired, used, or wrong-recipient tokens without a membership change.
|
|
48982
|
-
- [ ]
|
|
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.
|
|
48983
49132
|
- [ ] Validation
|
|
48984
|
-
- [ ] Run
|
|
48985
|
-
- [ ]
|
|
49133
|
+
- [ ] Run the service contract scenarios; use grouped fixtures and clear Arrange/Act/Assert blocks.
|
|
49134
|
+
- [ ] Run relevant lint and format checks.
|
|
48986
49135
|
|
|
48987
49136
|
### Phase 2: Expose and deliver invitations
|
|
48988
49137
|
|
|
48989
49138
|
- [ ] Add owner creation and recipient acceptance handlers in \`src/http/invitations.ts\` with safe error responses and creation rate limits.
|
|
48990
49139
|
- [ ] Queue delivery after persistence, retry transient mail failures, and expose the pending state to owners.
|
|
48991
|
-
- [ ]
|
|
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.
|
|
48992
49141
|
- [ ] Validation
|
|
48993
|
-
- [ ] Run
|
|
48994
|
-
- [ ]
|
|
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.
|
|
48995
49145
|
|
|
48996
49146
|
## References
|
|
48997
49147
|
|
|
@@ -49022,17 +49172,77 @@ Readers should retain access to historical activity while the team changes stora
|
|
|
49022
49172
|
|
|
49023
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.
|
|
49024
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
|
+
|
|
49025
49197
|
### Infrastructure Changes
|
|
49026
49198
|
|
|
49027
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.
|
|
49028
49200
|
|
|
49029
49201
|
### Interfaces
|
|
49030
49202
|
|
|
49031
|
-
|
|
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
|
+
\`\`\`
|
|
49032
49209
|
|
|
49033
49210
|
### Consequences
|
|
49034
49211
|
|
|
49035
|
-
|
|
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.
|
|
49036
49246
|
|
|
49037
49247
|
## Phases
|
|
49038
49248
|
|
|
@@ -49040,28 +49250,29 @@ Dual writes increase latency and create a risk of partial failure. Record discre
|
|
|
49040
49250
|
|
|
49041
49251
|
- [ ] Provision the new store and credentials; add write-failure metrics for both stores.
|
|
49042
49252
|
- [ ] Make \`src/activity/writer.ts\` write idempotently to both stores with stable activity IDs while retaining the legacy reader.
|
|
49043
|
-
- [ ]
|
|
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.
|
|
49044
49254
|
- [ ] Validation
|
|
49045
|
-
- [ ] Run writer
|
|
49046
|
-
- [ ]
|
|
49255
|
+
- [ ] Run public writer contract scenarios; confirm write-failure metrics are visible.
|
|
49256
|
+
- [ ] Run relevant lint and format checks.
|
|
49047
49257
|
|
|
49048
49258
|
### Phase 2: Backfill and compare
|
|
49049
49259
|
|
|
49050
49260
|
- [ ] Add resumable, idempotent batches in \`src/activity/backfill.ts\` without replacing newer dual-written records.
|
|
49051
49261
|
- [ ] Compare record IDs, counts, and ordering per project; report mismatches before any reader switch.
|
|
49052
|
-
- [ ]
|
|
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.
|
|
49053
49263
|
- [ ] Validation
|
|
49054
|
-
- [ ] Run
|
|
49055
|
-
- [ ]
|
|
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.
|
|
49056
49266
|
|
|
49057
49267
|
### Phase 3: Switch reads with rollback
|
|
49058
49268
|
|
|
49059
49269
|
- [ ] Route \`listActivity(projectId, cursor)\` through a reversible flag in \`src/activity/reader.ts\` and preserve cursor compatibility.
|
|
49060
|
-
- [ ]
|
|
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.
|
|
49061
49271
|
- [ ] Keep dual writes and monitor parity through the rollback window.
|
|
49062
49272
|
- [ ] Validation
|
|
49063
|
-
- [ ] Run
|
|
49064
|
-
- [ ]
|
|
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.
|
|
49065
49276
|
|
|
49066
49277
|
## References
|
|
49067
49278
|
|
|
@@ -49246,54 +49457,97 @@ Enable for a small cohort, compare completion and feedback, then expand or disab
|
|
|
49246
49457
|
var init_prd_onboarding = () => {};
|
|
49247
49458
|
|
|
49248
49459
|
// templates/examples/review-change.md
|
|
49249
|
-
var review_change_default = `# Review: invitation
|
|
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.
|
|
49250
49465
|
|
|
49251
|
-
|
|
49252
|
-
- **Intent:** Reject expired invitations without creating membership.
|
|
49253
|
-
- **Reviewer:** Example reviewer
|
|
49466
|
+
This is a fictional example. A real review must use its runtime model name in attribution.
|
|
49254
49467
|
|
|
49255
49468
|
## Intent Delivery
|
|
49256
49469
|
|
|
49257
|
-
The acceptance path
|
|
49470
|
+
The change adds an acceptance path. Helper tests do not establish that the request saves membership or leaves it unchanged on rejection.
|
|
49258
49471
|
|
|
49259
49472
|
## Readability
|
|
49260
49473
|
|
|
49261
|
-
|
|
49474
|
+
\`loadInvitation\` consumes the token and creates membership. I suggest \`acceptInvitation\` so callers can see the write.
|
|
49262
49475
|
|
|
49263
49476
|
## Semantic Organization
|
|
49264
49477
|
|
|
49265
|
-
|
|
49478
|
+
The handler maps HTTP to the service. The store owns persistence. No refactor is needed here.
|
|
49266
49479
|
|
|
49267
49480
|
## Maintainability
|
|
49268
49481
|
|
|
49269
|
-
The
|
|
49482
|
+
The change follows the existing store pattern and reuses invitation status.
|
|
49270
49483
|
|
|
49271
49484
|
## Reliability/Testing
|
|
49272
49485
|
|
|
49273
|
-
|
|
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\`.
|
|
49274
49511
|
|
|
49275
49512
|
## Performance
|
|
49276
49513
|
|
|
49277
|
-
|
|
49514
|
+
SKIPPED: no concrete performance regression is shown and no deep performance audit was requested.
|
|
49278
49515
|
|
|
49279
49516
|
## Security
|
|
49280
49517
|
|
|
49281
|
-
|
|
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]
|
|
49282
49535
|
|
|
49283
49536
|
## Findings
|
|
49284
49537
|
|
|
49285
|
-
| Severity | Location
|
|
49286
|
-
|
|
|
49287
|
-
| consider | \`src/invitations.ts:
|
|
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. |
|
|
49288
49542
|
|
|
49289
49543
|
## Overall Assessment
|
|
49290
49544
|
|
|
49291
|
-
|
|
49545
|
+
I recommend clearer API naming and acceptance/rejection scenarios. No private-helper coverage or speculative clock-skew request is needed.
|
|
49292
49546
|
|
|
49293
49547
|
## Verification and Limits
|
|
49294
49548
|
|
|
49295
|
-
- **Checks run:**
|
|
49296
|
-
- **Not verified:**
|
|
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.
|
|
49297
49551
|
`;
|
|
49298
49552
|
var init_review_change = () => {};
|
|
49299
49553
|
|
|
@@ -52070,7 +52324,7 @@ function createProgram(write = console.log, version = "0.1.0", options = {}) {
|
|
|
52070
52324
|
|
|
52071
52325
|
// src/index.ts
|
|
52072
52326
|
init_errors3();
|
|
52073
|
-
var version2 = "0.
|
|
52327
|
+
var version2 = "0.9.0";
|
|
52074
52328
|
try {
|
|
52075
52329
|
await createProgram(console.log, version2).parseAsync(process.argv);
|
|
52076
52330
|
} catch (error) {
|