review-workspace 1.1.0 → 1.2.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.
@@ -1,23 +1,3 @@
1
- function formatTarget(target) {
2
- switch (target.type) {
3
- case 'file':
4
- return target.path;
5
- case 'hunk':
6
- return `${target.path}#hunk${target.hunkIndex}`;
7
- case 'binary':
8
- return `${target.path} (binary)`;
9
- case 'line':
10
- return `${target.path}:${target.line} (${target.side})`;
11
- }
12
- }
13
- export function buildConcernComment(concern) {
14
- const prefix = concern.target ? `[${formatTarget(concern.target)}] ` : '';
15
- return `${prefix}${concern.note}`;
16
- }
17
- /** One comment per Concern, plain text — pasteable individually into an external tool. */
18
- export function buildConcernComments(state) {
19
- return state.concerns.map(buildConcernComment);
20
- }
21
1
  /**
22
2
  * A self-contained Markdown report — no server needed to view it afterward.
23
3
  * Read-only with respect to the bundle: never mutates state.json or questions.jsonl.
@@ -41,16 +21,6 @@ export function buildReport(document, state) {
41
21
  }
42
22
  }
43
23
  lines.push('');
44
- lines.push('## Concerns');
45
- if (state.concerns.length === 0) {
46
- lines.push('_None raised._');
47
- }
48
- else {
49
- for (const comment of buildConcernComments(state)) {
50
- lines.push(`- ${comment}`);
51
- }
52
- }
53
- lines.push('');
54
24
  lines.push('## Notes');
55
25
  if (state.notes.length === 0) {
56
26
  lines.push('_None._');
@@ -1,6 +1,9 @@
1
1
  import { appendFileSync, existsSync, readFileSync } from 'node:fs';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { join } from 'node:path';
4
+ // File name predates the Comment/kind split (it only ever held Questions) — kept
5
+ // as-is so bundles created before this change keep reading correctly; only the
6
+ // in-memory shape gained `kind`, not the on-disk path.
4
7
  function logPath(bundlePath) {
5
8
  return join(bundlePath, 'questions.jsonl');
6
9
  }
@@ -17,15 +20,22 @@ function readLog(bundlePath) {
17
20
  function appendEntry(bundlePath, entry) {
18
21
  appendFileSync(logPath(bundlePath), JSON.stringify(entry) + '\n');
19
22
  }
20
- /** Raises a new, immutable Question. Never mutates or removes any existing log entry. */
23
+ /** Raises a new, immutable question Comment. Never mutates or removes any existing log entry. */
21
24
  export function raiseQuestion(bundlePath, body, target) {
22
- const entry = { type: 'raised', id: randomUUID(), createdAt: new Date().toISOString(), body, target };
25
+ const entry = {
26
+ type: 'raised',
27
+ id: randomUUID(),
28
+ createdAt: new Date().toISOString(),
29
+ body,
30
+ target,
31
+ kind: 'question',
32
+ };
23
33
  appendEntry(bundlePath, entry);
24
- return { id: entry.id, createdAt: entry.createdAt, body: entry.body, target: entry.target, status: 'open' };
34
+ return { id: entry.id, createdAt: entry.createdAt, body: entry.body, target: entry.target, kind: 'question', status: 'open' };
25
35
  }
26
36
  /**
27
- * Withdraws an existing Question and raises its replacement in one call — a
28
- * correction is recorded as new log entries, never as an edit to the original.
37
+ * Withdraws an existing question Comment and raises its replacement in one call —
38
+ * a correction is recorded as new log entries, never as an edit to the original.
29
39
  */
30
40
  export function withdrawAndReplaceQuestion(bundlePath, questionId, replacementBody, replacementTarget) {
31
41
  const replacement = {
@@ -34,6 +44,7 @@ export function withdrawAndReplaceQuestion(bundlePath, questionId, replacementBo
34
44
  createdAt: new Date().toISOString(),
35
45
  body: replacementBody,
36
46
  target: replacementTarget,
47
+ kind: 'question',
37
48
  };
38
49
  const withdrawal = {
39
50
  type: 'withdrawn',
@@ -51,30 +62,32 @@ export function withdrawAndReplaceQuestion(bundlePath, questionId, replacementBo
51
62
  createdAt: replacement.createdAt,
52
63
  body: replacement.body,
53
64
  target: replacement.target,
65
+ kind: 'question',
54
66
  status: 'open',
55
67
  },
56
68
  };
57
69
  }
58
- /** Reduces the append-only log into the current set of Questions. */
70
+ /** Reduces the append-only log into the current set of Comments. */
59
71
  export function readQuestions(bundlePath) {
60
- const questions = new Map();
72
+ const comments = new Map();
61
73
  for (const entry of readLog(bundlePath)) {
62
74
  if (entry.type === 'raised') {
63
- questions.set(entry.id, {
75
+ comments.set(entry.id, {
64
76
  id: entry.id,
65
77
  createdAt: entry.createdAt,
66
78
  body: entry.body,
67
79
  target: entry.target,
80
+ kind: entry.kind ?? 'question',
68
81
  status: 'open',
69
82
  });
70
83
  }
71
84
  else {
72
- const question = questions.get(entry.questionId);
73
- if (question) {
74
- question.status = 'withdrawn';
75
- question.supersededBy = entry.replacementId;
85
+ const comment = comments.get(entry.questionId);
86
+ if (comment) {
87
+ comment.status = 'withdrawn';
88
+ comment.supersededBy = entry.replacementId;
76
89
  }
77
90
  }
78
91
  }
79
- return [...questions.values()].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
92
+ return [...comments.values()].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
80
93
  }
@@ -1,6 +1,5 @@
1
1
  export const EMPTY_REVIEW_STATE = {
2
2
  groups: {},
3
- concerns: [],
4
3
  notes: [],
5
4
  decision: 'unset',
6
5
  };
@@ -8,7 +8,7 @@ import { watchBundle } from "./watch-bundle.js";
8
8
  import { raiseQuestion, readQuestions, withdrawAndReplaceQuestion } from "../questions/questions-log.js";
9
9
  import { readReviewState, writeReviewState } from "../review-state/review-state.js";
10
10
  import { render } from "../renderer/render.js";
11
- import { buildConcernComments, buildReport } from "../export/report.js";
11
+ import { buildReport } from "../export/report.js";
12
12
  import { resolveAssetPath } from "../security/asset-path.js";
13
13
  export function generateWriteToken() {
14
14
  return randomBytes(24).toString('hex');
@@ -118,10 +118,6 @@ export function startReviewServer(bundlePath, opts = {}) {
118
118
  res.end(buildReport(latest.document, readReviewState(bundlePath)));
119
119
  return;
120
120
  }
121
- if (req.method === 'GET' && url.pathname === '/concerns') {
122
- respond(200, buildConcernComments(readReviewState(bundlePath)));
123
- return;
124
- }
125
121
  if (req.method === 'GET' && url.pathname.startsWith('/assets/')) {
126
122
  const assetPath = decodeURIComponent(url.pathname.slice('/assets/'.length));
127
123
  const resolution = resolveAssetPath(bundlePath, assetPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "review-workspace",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A local, offline decision surface for reviewing a code Comparison — evidence-linked map of intent, behavior, risk, and observable effects.",
5
5
  "license": "MIT",
6
6
  "author": "Salih Sayer",