reflection-check 0.0.1

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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/adapters/route-manifest.d.ts +3 -0
  4. package/dist/adapters/route-manifest.js +98 -0
  5. package/dist/adapters/route-manifest.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.js +93 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/doctor.d.ts +4 -0
  10. package/dist/commands/doctor.js +5 -0
  11. package/dist/commands/doctor.js.map +1 -0
  12. package/dist/commands/gc.d.ts +8 -0
  13. package/dist/commands/gc.js +45 -0
  14. package/dist/commands/gc.js.map +1 -0
  15. package/dist/commands/review.d.ts +7 -0
  16. package/dist/commands/review.js +149 -0
  17. package/dist/commands/review.js.map +1 -0
  18. package/dist/commands/run.d.ts +10 -0
  19. package/dist/commands/run.js +168 -0
  20. package/dist/commands/run.js.map +1 -0
  21. package/dist/commands/update.d.ts +11 -0
  22. package/dist/commands/update.js +183 -0
  23. package/dist/commands/update.js.map +1 -0
  24. package/dist/contracts/browser/assertions.d.ts +34 -0
  25. package/dist/contracts/browser/assertions.js +87 -0
  26. package/dist/contracts/browser/assertions.js.map +1 -0
  27. package/dist/contracts/browser/browser-contract.d.ts +13 -0
  28. package/dist/contracts/browser/browser-contract.js +35 -0
  29. package/dist/contracts/browser/browser-contract.js.map +1 -0
  30. package/dist/contracts/browser/console-observer.d.ts +6 -0
  31. package/dist/contracts/browser/console-observer.js +14 -0
  32. package/dist/contracts/browser/console-observer.js.map +1 -0
  33. package/dist/contracts/browser/overflow-check.d.ts +6 -0
  34. package/dist/contracts/browser/overflow-check.js +15 -0
  35. package/dist/contracts/browser/overflow-check.js.map +1 -0
  36. package/dist/contracts/browser/route-runner.d.ts +21 -0
  37. package/dist/contracts/browser/route-runner.js +98 -0
  38. package/dist/contracts/browser/route-runner.js.map +1 -0
  39. package/dist/contracts/component/component-visual-contract.d.ts +30 -0
  40. package/dist/contracts/component/component-visual-contract.js +147 -0
  41. package/dist/contracts/component/component-visual-contract.js.map +1 -0
  42. package/dist/contracts/design/command-adapter.d.ts +17 -0
  43. package/dist/contracts/design/command-adapter.js +60 -0
  44. package/dist/contracts/design/command-adapter.js.map +1 -0
  45. package/dist/contracts/design/design-contract.d.ts +8 -0
  46. package/dist/contracts/design/design-contract.js +149 -0
  47. package/dist/contracts/design/design-contract.js.map +1 -0
  48. package/dist/contracts/visual/baseline-compare.d.ts +19 -0
  49. package/dist/contracts/visual/baseline-compare.js +94 -0
  50. package/dist/contracts/visual/baseline-compare.js.map +1 -0
  51. package/dist/contracts/visual/image-diff.d.ts +27 -0
  52. package/dist/contracts/visual/image-diff.js +58 -0
  53. package/dist/contracts/visual/image-diff.js.map +1 -0
  54. package/dist/contracts/visual/thresholds.d.ts +15 -0
  55. package/dist/contracts/visual/thresholds.js +11 -0
  56. package/dist/contracts/visual/thresholds.js.map +1 -0
  57. package/dist/contracts/visual/visual-contract.d.ts +11 -0
  58. package/dist/contracts/visual/visual-contract.js +32 -0
  59. package/dist/contracts/visual/visual-contract.js.map +1 -0
  60. package/dist/core/artifact-store.d.ts +18 -0
  61. package/dist/core/artifact-store.js +105 -0
  62. package/dist/core/artifact-store.js.map +1 -0
  63. package/dist/core/baseline-store.d.ts +18 -0
  64. package/dist/core/baseline-store.js +56 -0
  65. package/dist/core/baseline-store.js.map +1 -0
  66. package/dist/core/config.d.ts +129 -0
  67. package/dist/core/config.js +159 -0
  68. package/dist/core/config.js.map +1 -0
  69. package/dist/core/define-reflection.d.ts +2 -0
  70. package/dist/core/define-reflection.js +4 -0
  71. package/dist/core/define-reflection.js.map +1 -0
  72. package/dist/core/exit-codes.d.ts +7 -0
  73. package/dist/core/exit-codes.js +9 -0
  74. package/dist/core/exit-codes.js.map +1 -0
  75. package/dist/core/failure-classifier.d.ts +3 -0
  76. package/dist/core/failure-classifier.js +19 -0
  77. package/dist/core/failure-classifier.js.map +1 -0
  78. package/dist/core/gc.d.ts +19 -0
  79. package/dist/core/gc.js +161 -0
  80. package/dist/core/gc.js.map +1 -0
  81. package/dist/core/manifest.d.ts +23 -0
  82. package/dist/core/manifest.js +21 -0
  83. package/dist/core/manifest.js.map +1 -0
  84. package/dist/core/redaction.d.ts +3 -0
  85. package/dist/core/redaction.js +63 -0
  86. package/dist/core/redaction.js.map +1 -0
  87. package/dist/core/report-schema.d.ts +262 -0
  88. package/dist/core/report-schema.js +112 -0
  89. package/dist/core/report-schema.js.map +1 -0
  90. package/dist/core/report-writer.d.ts +4 -0
  91. package/dist/core/report-writer.js +77 -0
  92. package/dist/core/report-writer.js.map +1 -0
  93. package/dist/core/server-manager.d.ts +23 -0
  94. package/dist/core/server-manager.js +64 -0
  95. package/dist/core/server-manager.js.map +1 -0
  96. package/dist/core/target-ir.d.ts +64 -0
  97. package/dist/core/target-ir.js +85 -0
  98. package/dist/core/target-ir.js.map +1 -0
  99. package/dist/index.d.ts +2 -0
  100. package/dist/index.js +2 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/integrations/playwright/browser-manager.d.ts +2 -0
  103. package/dist/integrations/playwright/browser-manager.js +5 -0
  104. package/dist/integrations/playwright/browser-manager.js.map +1 -0
  105. package/dist/integrations/playwright/context-factory.d.ts +7 -0
  106. package/dist/integrations/playwright/context-factory.js +19 -0
  107. package/dist/integrations/playwright/context-factory.js.map +1 -0
  108. package/dist/integrations/playwright/trace-policy.d.ts +5 -0
  109. package/dist/integrations/playwright/trace-policy.js +7 -0
  110. package/dist/integrations/playwright/trace-policy.js.map +1 -0
  111. package/dist/integrations/storybook/index-json.d.ts +21 -0
  112. package/dist/integrations/storybook/index-json.js +44 -0
  113. package/dist/integrations/storybook/index-json.js.map +1 -0
  114. package/dist/integrations/storybook/server.d.ts +8 -0
  115. package/dist/integrations/storybook/server.js +23 -0
  116. package/dist/integrations/storybook/server.js.map +1 -0
  117. package/dist/integrations/storybook/story-url.d.ts +2 -0
  118. package/dist/integrations/storybook/story-url.js +13 -0
  119. package/dist/integrations/storybook/story-url.js.map +1 -0
  120. package/dist/utils/process.d.ts +9 -0
  121. package/dist/utils/process.js +69 -0
  122. package/dist/utils/process.js.map +1 -0
  123. package/docs/agent-workflows.md +146 -0
  124. package/docs/artifacts-and-gc.md +125 -0
  125. package/docs/browser-contract.md +98 -0
  126. package/docs/ci.md +44 -0
  127. package/docs/configuration.md +210 -0
  128. package/docs/getting-started.md +166 -0
  129. package/docs/plans/reflection-implementation-plan.md +898 -0
  130. package/docs/target-ir-and-adapters.md +111 -0
  131. package/docs/validation-process.md +172 -0
  132. package/docs/visual-contract.md +174 -0
  133. package/package.json +62 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Niklas Westman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # Reflection
2
+
3
+ Reflection is a CLI for evidence-backed rendered UI validation.
4
+
5
+ It answers three practical questions for frontend changes:
6
+
7
+ ```text
8
+ Does the UI still work?
9
+ Does it still match the design system contract?
10
+ Did anything visible change unexpectedly?
11
+ ```
12
+
13
+ Current status: Reflection now has the core local validation loop: config → browser route check → screenshot/visual evidence → report → review → explicit baseline update.
14
+
15
+ ## Documentation
16
+
17
+ - [`docs/getting-started.md`](docs/getting-started.md) — add Reflection to a new repo and run the local loop.
18
+ - [`docs/configuration.md`](docs/configuration.md) — supported config shape, run modes, browser/design/component contracts.
19
+ - [`docs/browser-contract.md`](docs/browser-contract.md) — rendered route expectations and screenshot evidence.
20
+ - [`docs/visual-contract.md`](docs/visual-contract.md) — route/component baselines, review-only diffs, and baseline update policy.
21
+ - [`docs/artifacts-and-gc.md`](docs/artifacts-and-gc.md) — report bundle layout, artifact roots, and safe garbage collection.
22
+ - [`docs/agent-workflows.md`](docs/agent-workflows.md) — agent completion loop and baseline-update rules.
23
+ - [`docs/validation-process.md`](docs/validation-process.md) — canonical operating guide for agents and CI.
24
+ - [`docs/ci.md`](docs/ci.md) — CI defaults and exit codes.
25
+ - [`docs/target-ir-and-adapters.md`](docs/target-ir-and-adapters.md) — internal target inventory and adapter seam.
26
+
27
+ ## Command surface
28
+
29
+ ```bash
30
+ reflection run
31
+ reflection review
32
+ reflection update
33
+ reflection doctor
34
+ ```
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pnpm add -D reflection-check
40
+ ```
41
+
42
+ Use the package root helper in `reflection.config.ts`:
43
+
44
+ ```ts
45
+ import { defineReflection } from 'reflection-check';
46
+ ```
47
+
48
+ ## Local development
49
+
50
+ ```bash
51
+ pnpm install
52
+ pnpm typecheck
53
+ pnpm test
54
+ pnpm build
55
+ ```
@@ -0,0 +1,3 @@
1
+ import type { TargetIR } from '../core/target-ir.js';
2
+ export declare function loadRouteManifestTargets(manifestPath: string): Promise<TargetIR>;
3
+ export declare function parseRouteManifestTargets(input: unknown): TargetIR;
@@ -0,0 +1,98 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { z } from 'zod';
3
+ const RouteManifestExpectationSchema = z.union([
4
+ z.object({ urlIncludes: z.string() }).strict(),
5
+ z.object({ urlEquals: z.string() }).strict(),
6
+ z.object({ role: z.string(), name: z.string().optional() }).strict(),
7
+ z.object({ label: z.string() }).strict(),
8
+ z.object({ text: z.string() }).strict(),
9
+ z.object({ noText: z.string() }).strict(),
10
+ z.object({ selector: z.string() }).strict(),
11
+ z.object({ elementVisible: z.string() }).strict(),
12
+ z.object({ elementNotVisible: z.string() }).strict(),
13
+ z.object({ noHorizontalOverflow: z.literal(true) }).strict(),
14
+ z.object({ noConsoleErrors: z.literal(true) }).strict(),
15
+ z.object({ screenshot: z.string() }).strict()
16
+ ]);
17
+ const RouteManifestRouteSchema = z
18
+ .object({
19
+ id: z.string().min(1),
20
+ name: z.string().min(1).optional(),
21
+ path: z.string().min(1),
22
+ viewports: z.array(z.string().min(1)).default(['desktop']),
23
+ expects: z.array(RouteManifestExpectationSchema).default([]),
24
+ blocking: z.boolean().optional()
25
+ })
26
+ .strict();
27
+ const RouteManifestSchema = z
28
+ .object({
29
+ project: z.string().min(1),
30
+ baseUrl: z.string().url(),
31
+ maskSelectors: z.array(z.string().min(1)).default([]),
32
+ routes: z.array(RouteManifestRouteSchema).default([])
33
+ })
34
+ .strict()
35
+ .superRefine((manifest, context) => {
36
+ const seen = new Set();
37
+ for (const [index, route] of manifest.routes.entries()) {
38
+ if (seen.has(route.id)) {
39
+ context.addIssue({
40
+ code: z.ZodIssueCode.custom,
41
+ path: ['routes', index, 'id'],
42
+ message: `duplicate route id "${route.id}"`
43
+ });
44
+ }
45
+ seen.add(route.id);
46
+ }
47
+ });
48
+ export async function loadRouteManifestTargets(manifestPath) {
49
+ let raw;
50
+ try {
51
+ raw = await readFile(manifestPath, 'utf8');
52
+ }
53
+ catch (error) {
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ throw new Error(`Route manifest not found: ${manifestPath}: ${message}`);
56
+ }
57
+ try {
58
+ return parseRouteManifestTargets(JSON.parse(raw));
59
+ }
60
+ catch (error) {
61
+ if (error instanceof SyntaxError) {
62
+ throw new Error(`Invalid route manifest JSON: ${manifestPath}: ${error.message}`);
63
+ }
64
+ throw error;
65
+ }
66
+ }
67
+ export function parseRouteManifestTargets(input) {
68
+ const manifest = parseRouteManifest(input);
69
+ return {
70
+ project: manifest.project,
71
+ targets: manifest.routes.map((route) => ({
72
+ id: route.id,
73
+ family: 'browser-route',
74
+ source: 'adapter',
75
+ runModes: ['smoke', 'full'],
76
+ blocking: route.blocking ?? true,
77
+ route: {
78
+ path: route.path,
79
+ ...(route.name !== undefined ? { name: route.name } : {}),
80
+ viewports: route.viewports,
81
+ expects: route.expects
82
+ },
83
+ browser: {
84
+ baseUrl: manifest.baseUrl,
85
+ maskSelectors: manifest.maskSelectors
86
+ }
87
+ }))
88
+ };
89
+ }
90
+ function parseRouteManifest(input) {
91
+ const parsed = RouteManifestSchema.safeParse(input);
92
+ if (!parsed.success) {
93
+ const details = parsed.error.issues.map((issue) => `${issue.path.join('.') || 'manifest'}: ${issue.message}`).join('; ');
94
+ throw new Error(`Invalid route manifest: ${details}`);
95
+ }
96
+ return parsed.data;
97
+ }
98
+ //# sourceMappingURL=route-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-manifest.js","sourceRoot":"","sources":["../../src/adapters/route-manifest.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ export declare function createCli(): Command;
package/dist/cli.js ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { Command, CommanderError } from 'commander';
5
+ import { doctorCommand } from './commands/doctor.js';
6
+ import { gcCommand } from './commands/gc.js';
7
+ import { reviewCommand } from './commands/review.js';
8
+ import { runCommand } from './commands/run.js';
9
+ import { updateCommand } from './commands/update.js';
10
+ import { ExitCode } from './core/exit-codes.js';
11
+ export function createCli() {
12
+ const program = new Command();
13
+ program
14
+ .name('reflection')
15
+ .description('Evidence-backed rendered UI validation.')
16
+ .version('0.0.1')
17
+ .configureOutput({
18
+ outputError: (message, write) => write(message)
19
+ });
20
+ program
21
+ .command('run')
22
+ .description('Check the UI contract for this project.')
23
+ .option('--config <path>', 'Path to reflection.config.ts')
24
+ .option('--mode <mode>', 'Run mode: smoke, design, visual, full', 'smoke')
25
+ .option('--ci', 'Run with CI defaults')
26
+ .option('--report-dir <path>', 'Artifact/report root directory')
27
+ .option('--workers [count]', 'Worker count for browser/visual checks')
28
+ .action(async (options) => {
29
+ await runCommand(options);
30
+ });
31
+ program
32
+ .command('review')
33
+ .description('Show what passed, failed, changed visually, and where the evidence is.')
34
+ .option('--latest', 'Review the latest run')
35
+ .option('--run <runId>', 'Review a specific run id')
36
+ .option('--json', 'Emit a stable JSON summary')
37
+ .option('--report-dir <path>', 'Artifact/report root directory')
38
+ .action(async (options) => {
39
+ await reviewCommand(options);
40
+ });
41
+ program
42
+ .command('update')
43
+ .description('Accept intentional visual changes by updating targeted baselines.')
44
+ .option('--config <path>', 'Path to reflection.config.ts')
45
+ .option('--report-dir <path>', 'Artifact/report root directory')
46
+ .option('--from-run <runId>', 'Run id to promote from, or latest', 'latest')
47
+ .option('--route <routeId>', 'Update visual baselines for a specific route id')
48
+ .option('--case <caseId>', 'Update a specific visual baseline case id')
49
+ .option('--all', 'Update all visual baselines from the selected run')
50
+ .option('--dry-run', 'Show planned baseline updates without writing')
51
+ .option('--ci', 'Refuse updates under CI mode')
52
+ .action(async (options) => {
53
+ await updateCommand(options);
54
+ });
55
+ program
56
+ .command('gc')
57
+ .description('Clean up old Reflection run artifacts without touching baselines.')
58
+ .option('--report-dir <path>', 'Artifact/report root directory')
59
+ .option('--dry-run', 'List eligible run directories without deleting them')
60
+ .option('--delete', 'Delete eligible run directories')
61
+ .action(async (options) => {
62
+ await gcCommand(options);
63
+ });
64
+ program
65
+ .command('doctor')
66
+ .description('Check whether Reflection can run correctly in this project.')
67
+ .option('--config <path>', 'Path to reflection.config.ts')
68
+ .action(async (options) => {
69
+ await doctorCommand(options);
70
+ });
71
+ return program;
72
+ }
73
+ async function main() {
74
+ const program = createCli();
75
+ try {
76
+ await program.parseAsync(process.argv);
77
+ }
78
+ catch (error) {
79
+ if (error instanceof CommanderError) {
80
+ if (error.message) {
81
+ console.error(error.message);
82
+ }
83
+ process.exit(error.exitCode);
84
+ }
85
+ console.error(error instanceof Error ? error.message : String(error));
86
+ process.exit(ExitCode.ToolOrConfigError);
87
+ }
88
+ }
89
+ const executedPath = process.argv[1] ? realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]) : false;
90
+ if (executedPath) {
91
+ void main();
92
+ }
93
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export type DoctorCommandOptions = {
2
+ config?: string;
3
+ };
4
+ export declare function doctorCommand(_options?: DoctorCommandOptions): Promise<void>;
@@ -0,0 +1,5 @@
1
+ export async function doctorCommand(_options = {}) {
2
+ console.log('Reflection doctor');
3
+ console.log('Phase 1.1 setup check placeholder: config/schema validation is available; runtime checks come next.');
4
+ }
5
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,WAAiC,EAAE;IACrE,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,qGAAqG,CAAC,CAAC;AACrH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type GcPlan } from '../core/gc.js';
2
+ export type GcCommandOptions = {
3
+ reportDir?: string;
4
+ dryRun?: boolean;
5
+ delete?: boolean;
6
+ };
7
+ export declare function gcCommand(options?: GcCommandOptions): Promise<void>;
8
+ export declare function renderGcSummary(plan: GcPlan): string;
@@ -0,0 +1,45 @@
1
+ import { CommanderError } from 'commander';
2
+ import { collectGarbage } from '../core/gc.js';
3
+ import { ExitCode } from '../core/exit-codes.js';
4
+ export async function gcCommand(options = {}) {
5
+ try {
6
+ if (options.dryRun === true && options.delete === true) {
7
+ throw new CommanderError(ExitCode.InvalidUsage, 'reflection.gc', 'Use either --dry-run or --delete, not both.');
8
+ }
9
+ const plan = await collectGarbage({ ...(options.reportDir !== undefined ? { reportDir: options.reportDir } : {}), dryRun: options.delete !== true });
10
+ console.log(renderGcSummary(plan));
11
+ }
12
+ catch (error) {
13
+ if (error instanceof CommanderError) {
14
+ throw error;
15
+ }
16
+ const message = error instanceof Error ? error.message : String(error);
17
+ throw new CommanderError(ExitCode.ToolOrConfigError, 'reflection.gc', message);
18
+ }
19
+ }
20
+ export function renderGcSummary(plan) {
21
+ const lines = ['Reflection GC', '', `Dry run: ${plan.dryRun ? 'yes' : 'no'}`, `Runs directory: ${plan.runsDir}`, ''];
22
+ if (plan.eligible.length === 0) {
23
+ lines.push(plan.dryRun ? 'No eligible run directories would be deleted.' : 'No eligible run directories were deleted.');
24
+ }
25
+ else {
26
+ lines.push(plan.dryRun ? 'Would delete:' : 'Eligible:');
27
+ for (const run of plan.eligible) {
28
+ lines.push(`- ${run.runId}: ${run.path}`);
29
+ }
30
+ }
31
+ if (plan.deleted.length > 0) {
32
+ lines.push('', 'Deleted:');
33
+ for (const run of plan.deleted) {
34
+ lines.push(`- ${run.runId}: ${run.path}`);
35
+ }
36
+ }
37
+ if (plan.skipped.length > 0) {
38
+ lines.push('', 'Skipped:');
39
+ for (const run of plan.skipped) {
40
+ lines.push(`- ${run.runId}: ${run.reason}`);
41
+ }
42
+ }
43
+ return lines.join('\n');
44
+ }
45
+ //# sourceMappingURL=gc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gc.js","sourceRoot":"","sources":["../../src/commands/gc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type ReviewCommandOptions = {
2
+ reportDir?: string;
3
+ latest?: boolean;
4
+ run?: string;
5
+ json?: boolean;
6
+ };
7
+ export declare function reviewCommand(options?: ReviewCommandOptions): Promise<void>;
@@ -0,0 +1,149 @@
1
+ import { readFile, realpath } from 'node:fs/promises';
2
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
3
+ import { CommanderError } from 'commander';
4
+ import { ExitCode } from '../core/exit-codes.js';
5
+ import { validateReport } from '../core/report-schema.js';
6
+ export async function reviewCommand(options = {}) {
7
+ try {
8
+ if (options.latest === true && options.run !== undefined) {
9
+ throw new Error('Use either --latest or --run, not both.');
10
+ }
11
+ const rootDir = resolve(options.reportDir ?? '.reflection');
12
+ const runsDir = resolve(rootDir, 'runs');
13
+ const runId = options.run ?? (await readLatestRunId(rootDir));
14
+ assertSafeRunId(runId);
15
+ const runDir = resolve(runsDir, runId);
16
+ ensureInside(runsDir, runDir);
17
+ await ensureRealPathInside(runsDir, runDir);
18
+ const reportPath = resolve(runDir, 'report.json');
19
+ ensureInside(runDir, reportPath);
20
+ await ensureRealPathInside(runDir, reportPath);
21
+ const report = validateReport(JSON.parse(await readFile(reportPath, 'utf8')));
22
+ if (report.runId !== runId) {
23
+ throw new Error(`Report run id mismatch: expected ${runId}, found ${report.runId}`);
24
+ }
25
+ const summary = createReviewSummary(report, reportPath, dirname(reportPath));
26
+ if (options.json === true) {
27
+ console.log(JSON.stringify(summary, null, 2));
28
+ return;
29
+ }
30
+ console.log(renderReview(summary));
31
+ }
32
+ catch (error) {
33
+ if (error instanceof CommanderError) {
34
+ throw error;
35
+ }
36
+ const message = error instanceof Error ? error.message : String(error);
37
+ throw new CommanderError(ExitCode.ToolOrConfigError, 'reflection.review', message);
38
+ }
39
+ }
40
+ async function readLatestRunId(rootDir) {
41
+ const runsDir = resolve(rootDir, 'runs');
42
+ const latestPath = resolve(runsDir, 'latest');
43
+ ensureInside(runsDir, latestPath);
44
+ await ensureRealPathInside(runsDir, latestPath);
45
+ const value = (await readFile(latestPath, 'utf8')).trim();
46
+ if (value.length === 0) {
47
+ throw new Error(`Latest Reflection run pointer is empty: ${latestPath}`);
48
+ }
49
+ return value;
50
+ }
51
+ function createReviewSummary(report, reportPath, runDir) {
52
+ const blockingFailures = report.checks.filter(isBlockingFailure).map(toReviewCheck);
53
+ const reviewItems = report.checks.filter(isReviewItem).map(toReviewCheck);
54
+ return {
55
+ runId: report.runId,
56
+ project: report.project,
57
+ status: report.status,
58
+ reportPath,
59
+ blockingFailures,
60
+ reviewItems,
61
+ artifactPaths: collectArtifactPaths(report, runDir),
62
+ suggestedNextSteps: report.suggestedNextSteps.map((step) => step.summary)
63
+ };
64
+ }
65
+ function renderReview(summary) {
66
+ const lines = [];
67
+ lines.push('Reflection review');
68
+ lines.push('');
69
+ lines.push(`Project: ${summary.project}`);
70
+ lines.push(`Run: ${summary.runId}`);
71
+ lines.push(`Status: ${summary.status}`);
72
+ lines.push(`Report: ${summary.reportPath}`);
73
+ if (summary.blockingFailures.length > 0) {
74
+ lines.push('');
75
+ lines.push('Blocking:');
76
+ for (const item of summary.blockingFailures) {
77
+ lines.push(`- ${item.id} — ${item.summary}`);
78
+ }
79
+ }
80
+ if (summary.reviewItems.length > 0) {
81
+ lines.push('');
82
+ lines.push('Review:');
83
+ for (const item of summary.reviewItems) {
84
+ lines.push(`- ${item.id} — ${item.summary}`);
85
+ }
86
+ }
87
+ if (summary.artifactPaths.length > 0) {
88
+ lines.push('');
89
+ lines.push('Artifacts:');
90
+ for (const artifactPath of summary.artifactPaths) {
91
+ lines.push(`- ${artifactPath}`);
92
+ }
93
+ }
94
+ if (summary.suggestedNextSteps.length > 0) {
95
+ lines.push('');
96
+ lines.push('Next:');
97
+ for (const step of summary.suggestedNextSteps) {
98
+ lines.push(`- ${step}`);
99
+ }
100
+ }
101
+ return lines.join('\n');
102
+ }
103
+ function isBlockingFailure(check) {
104
+ return check.severity === 'blocking' && (check.status === 'fail' || check.status === 'error');
105
+ }
106
+ function isReviewItem(check) {
107
+ return check.severity === 'review' && (check.status === 'warn' || check.status === 'fail');
108
+ }
109
+ function toReviewCheck(check) {
110
+ return {
111
+ id: check.id,
112
+ summary: check.summary,
113
+ target: check.target,
114
+ status: check.status
115
+ };
116
+ }
117
+ function collectArtifactPaths(report, runDir) {
118
+ const paths = new Set();
119
+ const addArtifact = (artifact) => {
120
+ assertSafeArtifactPath(artifact.path, runDir);
121
+ paths.add(artifact.path);
122
+ };
123
+ report.artifacts.forEach(addArtifact);
124
+ report.checks.flatMap((check) => check.artifacts).forEach(addArtifact);
125
+ return [...paths];
126
+ }
127
+ function assertSafeArtifactPath(artifactPath, runDir) {
128
+ if (isAbsolute(artifactPath)) {
129
+ throw new Error(`Invalid absolute artifact path in report: ${artifactPath}`);
130
+ }
131
+ const resolvedArtifactPath = resolve(runDir, artifactPath);
132
+ ensureInside(runDir, resolvedArtifactPath, `Refusing to emit artifact path outside Reflection run directory: ${artifactPath}`);
133
+ }
134
+ function assertSafeRunId(runId) {
135
+ if (!/^[A-Za-z0-9._:-]+$/.test(runId) || isAbsolute(runId) || runId.split(/[\\/]/).includes('..')) {
136
+ throw new Error(`Invalid Reflection run id: ${runId}`);
137
+ }
138
+ }
139
+ async function ensureRealPathInside(parent, child) {
140
+ const [realParent, realChild] = await Promise.all([realpath(parent), realpath(child)]);
141
+ ensureInside(realParent, realChild, `Refusing to read report outside Reflection runs directory: ${child}`);
142
+ }
143
+ function ensureInside(parent, child, message) {
144
+ const relation = relative(parent, child);
145
+ if (relation.startsWith('..') || isAbsolute(relation)) {
146
+ throw new Error(message ?? `Refusing to read report outside Reflection runs directory: ${child}`);
147
+ }
148
+ }
149
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type RunMode } from '../core/config.js';
2
+ export type RunCommandOptions = {
3
+ config?: string;
4
+ mode: string;
5
+ ci?: boolean;
6
+ reportDir?: string;
7
+ workers?: string | number | boolean;
8
+ };
9
+ export declare function parseRunMode(value: string): RunMode;
10
+ export declare function runCommand(options: RunCommandOptions): Promise<void>;