@davmash/bygone 0.8.14 → 0.8.16

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.8.16
6
+
7
+ - Clarified authored-tour errors when a document requires a newer format version
8
+ - Made tag-triggered npm release checks fetch repository history needed by checked-in tour fixtures
9
+
10
+ ## 0.8.15
11
+
12
+ - Fixed v2 deconstructed-tour coverage so authored Final-mode walkthrough anchors determine coverage while Explanation assignment completeness remains a separate metric
13
+ - Added distinct Final walkthrough coverage and Explanation assignment indicators to authored tours in the Present sidebar
14
+
5
15
  ## 0.8.14
6
16
 
7
17
  - Reorganized desktop menus around familiar File, Edit, Git, Present, Navigate, View, and Window responsibilities while exposing file, directory, revision, branch, history, tour, and blank-comparison workflows
package/README.md CHANGED
@@ -123,7 +123,7 @@ Change tours can layer an authored, code-connected narrative over the complete g
123
123
 
124
124
  Packaged macOS builds register `.bygone` with the desktop app, so Finder can open a presentation directly. Windows and Linux builds currently require opening the file through Bygone or the `bygone` command; they do not install an operating-system file association.
125
125
 
126
- Agents can validate anchors and structure with `bygone tour validate <file.bygone> --json`, compile a portable manifest with `bygone tour compile`, and retrieve the authoring contract with `bygone tour schema`. See [Generating change tours with an LLM](./docs/generating-change-tours.md).
126
+ Agents can validate anchors and structure with `bygone tour validate <file.bygone> --json`, compile a repository-bound manifest with `bygone tour compile`, and retrieve the authoring contract with `bygone tour schema`. See [Generating change tours with an LLM](./docs/generating-change-tours.md).
127
127
 
128
128
  Install the agent skill:
129
129
 
@@ -210,7 +210,7 @@ Publishing is intentionally explicit:
210
210
  npm run release:publish
211
211
  ```
212
212
 
213
- Run `release:publish` after committing the version and release changes. It aborts unless the worktree is clean and the current branch is `main`, pushes `main`, waits for that exact commit's GitHub Release Check workflow, rebuilds and installs all local artifacts, publishes the npm package, creates a GitHub release for desktop artifacts, and updates the Homebrew tap. It launches npm browser login when needed and pauses immediately before an unpublished version's time-limited passkey challenge. The tap checkout defaults to the sibling `homebrew-bygone` directory; set `BYGONE_HOMEBREW_TAP` to override it. Until Marketplace trusted-publisher policies are publicly configurable, upload the packaged VSIX from the Visual Studio Marketplace publisher page.
213
+ Run `release:publish` after committing the version and release changes. It aborts unless the worktree is clean and the current branch is `main`, pushes `main`, waits for that exact commit's GitHub Release Check workflow, rebuilds and installs all local artifacts, creates a GitHub release for desktop artifacts, and updates the Homebrew tap. The release tag triggers the protected GitHub Actions Release workflow, which publishes the npm package through trusted publishing after approval. The tap checkout defaults to the sibling `homebrew-bygone` directory; set `BYGONE_HOMEBREW_TAP` to override it. Until Marketplace trusted-publisher policies are publicly configurable, upload the packaged VSIX from the Visual Studio Marketplace publisher page.
214
214
 
215
215
  Reinstall the current built artifacts without rebuilding:
216
216
 
@@ -10,6 +10,7 @@ const CLI_SPEC = Object.freeze({
10
10
  'bygone --diff <file1> <file2> <file3> [...]',
11
11
  'bygone --history <path>',
12
12
  'bygone --git-diff <ref1> <ref2> [<ref3>...]',
13
+ 'bygone --read-only <desktop-arguments...>',
13
14
  'bygone review [<head>] [--base <base>]',
14
15
  'bygone -C <directory> <command-or-path> [...]',
15
16
  'bygone present [<head>] [--base <base>] [--tour <file.bygone>]',
@@ -107,6 +108,13 @@ const CLI_SPEC = Object.freeze({
107
108
  description: 'Use an authored .bygone presentation',
108
109
  argument: 'path'
109
110
  },
111
+ {
112
+ id: 'readOnly',
113
+ kind: 'global',
114
+ tokens: ['--read-only'],
115
+ description: 'Open live Explore sources without allowing edits',
116
+ argument: 'none'
117
+ },
110
118
  {
111
119
  id: 'directory',
112
120
  kind: 'global',
@@ -144,13 +152,15 @@ const CLI_SPEC = Object.freeze({
144
152
  'Two positional paths auto-select file diff or directory compare.',
145
153
  'Three or more positional paths auto-select multi-panel file diff or multi-directory compare.',
146
154
  '`--git-diff` accepts branches, tags, SHAs, HEAD~1, stash@{0}, INDEX, and WORKTREE.',
155
+ 'In `--git-diff`, committed refs and INDEX are read-only; WORKTREE (also WORKDIR or WORKINGTREE) is live and writable.',
156
+ '`--read-only` prevents editing every live file or WORKTREE panel in the launched Explore session.',
147
157
  '`review` compares merge-base(BASE,HEAD) with HEAD and detects the default base when omitted.',
148
158
  '`present` turns the same range into an app-hosted, ordered change tour.',
149
159
  '`-C <directory>` resolves relative paths and Git refs from that directory without changing the shell working directory.',
150
160
  '`tour validate` resolves every authored anchor; add `--json` for agent-readable output.',
151
- '`tour compile` writes a portable manifest to stdout or `--output`; `tour schema` prints its source schema.',
161
+ '`tour compile` writes a repository-bound manifest to stdout or `--output`; `tour schema` prints its source schema.',
152
162
  '`tour context` emits compact, structured Git evidence for an LLM without invoking a model.',
153
- '`tour coverage` reports referenced changed hunks and author-declared explanation depth.',
163
+ '`tour coverage` reports authored walkthrough anchor coverage and author-declared explanation depth; for v2 tours it reads Final mode and reports deconstructed Explanation assignment separately, while `--minimum-coverage` gates the walkthrough metric.',
154
164
  '`--branch-diff` is retained as an alias for `review`.',
155
165
  '`completion` prints a completion script for Zsh, Bash, or Fish.',
156
166
  'In the standalone app, drop 1 file for history, 2 files/directories for compare, or 3+ matching paths for multi-panel compare.'
@@ -75,7 +75,7 @@ ${legacyOptions}
75
75
  ${historyOptions}
76
76
  )
77
77
  shells=('zsh:Z shell' 'bash:Bash' 'fish:Fish shell')
78
- tour_actions=('context:Build structured change evidence' 'coverage:Measure authored tour coverage' 'validate:Validate source and anchors' 'compile:Compile a portable manifest' 'schema:Print the source schema')
78
+ tour_actions=('context:Build structured change evidence' 'coverage:Measure authored tour coverage' 'validate:Validate source and anchors' 'compile:Compile a repository-bound manifest' 'schema:Print the source schema')
79
79
 
80
80
  if (( CURRENT == 2 )); then
81
81
  _describe 'bygone command' root_items
package/cli/present.js CHANGED
@@ -3,9 +3,11 @@ const { createServer } = require('http');
3
3
  const path = require('path');
4
4
  const { spawn } = require('child_process');
5
5
  const { buildChangeTourManifest, parseChangeTourStory } = require('../out/changeTour.js');
6
+ const { buildTourAuthoringCoverage, buildTourCoverageReport } = require('../out/tourCoverage.js');
6
7
  const { buildTourWindowTitle } = require('../out/windowTitle.js');
7
8
  const { tokenMatches } = require('./commandSpec.js');
8
9
  const { loadTourSource } = require('./tourFile.js');
10
+ const { createTourHistory } = require('./tourHistory.js');
9
11
 
10
12
  const MIME_TYPES = new Map([
11
13
  ['.css', 'text/css; charset=utf-8'],
@@ -23,7 +25,7 @@ async function startPresentation(args, cwd, packageRoot, options = {}) {
23
25
  ? parseChangeTourStory(JSON.parse(readFileSync(path.resolve(cwd, process.env.BYGONE_TOUR_STORY), 'utf8')))
24
26
  : undefined;
25
27
  const source = tourPath ? loadTourSource(cwd, tourPath).source : undefined;
26
- const manifest = buildChangeTourManifest(cwd, {
28
+ const builtManifest = buildChangeTourManifest(cwd, {
27
29
  headRef: explicitHeadRef || source?.range?.head || headRef,
28
30
  baseRef: baseRef || source?.range?.base,
29
31
  title: process.env.BYGONE_TOUR_TITLE,
@@ -31,6 +33,10 @@ async function startPresentation(args, cwd, packageRoot, options = {}) {
31
33
  story,
32
34
  source
33
35
  });
36
+ const manifest = source
37
+ ? { ...builtManifest, authoringCoverage: buildTourAuthoringCoverage(buildTourCoverageReport(cwd, source)) }
38
+ : builtManifest;
39
+ const history = createTourHistory(manifest);
34
40
  const serializedManifest = `${JSON.stringify(manifest, null, 2)}\n`;
35
41
  const outputPath = process.env.BYGONE_TOUR_OUTPUT;
36
42
  if (outputPath) {
@@ -46,6 +52,31 @@ async function startPresentation(args, cwd, packageRoot, options = {}) {
46
52
 
47
53
  const server = createServer((request, response) => {
48
54
  const requestUrl = new URL(request.url || '/', 'http://127.0.0.1');
55
+ if (requestUrl.pathname === '/history/list' || requestUrl.pathname === '/history/diff') {
56
+ if (request.method !== 'POST') return respondJson(response, 405, { error: 'Method not allowed' });
57
+ if (!isSameOriginLoopbackRequest(request)) return respondJson(response, 403, { error: 'Forbidden' });
58
+ if (!history) return respondJson(response, 404, { error: 'History requires a version 2 tour.' });
59
+ let body = '';
60
+ request.setEncoding('utf8');
61
+ request.on('data', chunk => {
62
+ body += chunk;
63
+ if (body.length > 8192) {
64
+ respondJson(response, 413, { error: 'Request too large' });
65
+ request.destroy();
66
+ }
67
+ });
68
+ request.on('end', () => {
69
+ if (response.writableEnded) return;
70
+ try {
71
+ const input = JSON.parse(body);
72
+ const result = requestUrl.pathname === '/history/list' ? history.list(input) : history.diff(input);
73
+ respondJson(response, 200, result);
74
+ } catch {
75
+ respondJson(response, 400, { error: 'Could not load history for this file and revision.' });
76
+ }
77
+ });
78
+ return;
79
+ }
49
80
  if (requestUrl.pathname === '/narration/claim') {
50
81
  if (request.method !== 'POST') {
51
82
  respond(response, 405, 'Method not allowed');
@@ -194,6 +225,16 @@ function respond(response, statusCode, message) {
194
225
  response.end(message);
195
226
  }
196
227
 
228
+ function respondJson(response, statusCode, body) {
229
+ if (!response.headersSent) {
230
+ response.writeHead(statusCode, {
231
+ 'Content-Type': 'application/json; charset=utf-8',
232
+ 'Cache-Control': 'no-store'
233
+ });
234
+ }
235
+ response.end(JSON.stringify(body));
236
+ }
237
+
197
238
  function isSameOriginLoopbackRequest(request) {
198
239
  const origin = request.headers.origin;
199
240
  const host = request.headers.host;
package/cli/tour.js CHANGED
@@ -27,7 +27,8 @@ function runTourCommand(args, cwd, packageRoot, output = process.stdout) {
27
27
  const report = buildTourCoverageReport(cwd, source);
28
28
  output.write(options.json ? `${JSON.stringify(report, null, 2)}\n` : renderCoverageReport(report));
29
29
  if (options.minimumCoverage !== undefined && report.totals.coveragePercent < options.minimumCoverage) {
30
- throw new Error(`Tour coverage ${report.totals.coveragePercent}% is below the required ${options.minimumCoverage}%.`);
30
+ const label = report.version === 2 ? 'Final walkthrough coverage' : 'Tour coverage';
31
+ throw new Error(`${label} ${report.totals.coveragePercent}% is below the required ${options.minimumCoverage}%.`);
31
32
  }
32
33
  return { action: 'coverage', sourcePath: resolvedPath, report };
33
34
  }
@@ -98,10 +99,20 @@ function parseTourArgs(args) {
98
99
  }
99
100
 
100
101
  function renderCoverageReport(report) {
102
+ const coverageLabel = report.version === 2 ? 'Final walkthrough coverage' : 'Tour coverage';
101
103
  const lines = [
102
- `Tour coverage: ${report.totals.coveredUnits}/${report.totals.includedUnits} hunks (${report.totals.coveragePercent}%)`,
104
+ `${coverageLabel}: ${report.totals.coveredUnits}/${report.totals.includedUnits} hunks (${report.totals.coveragePercent}%)`,
103
105
  `Depth: ${report.depth.mentioned} mentioned · ${report.depth.explained} explained · ${report.depth.contextualized} contextualized`
104
106
  ];
107
+ if (report.version === 2) {
108
+ for (const assignment of report.explanationAssignments) {
109
+ const suffix = report.explanationAssignments.length > 1 ? ` (${assignment.sceneId})` : '';
110
+ lines.push(
111
+ `Explanation assignment${suffix}: ${assignment.completeUnits}/${assignment.totalUnits} hunks `
112
+ + `(${assignment.assignmentPercent}%; ${assignment.assignedUnits} assigned, ${assignment.excludedUnits} excluded)`
113
+ );
114
+ }
115
+ }
105
116
  if (report.totals.excludedUnits > 0) lines.push(`Excluded: ${report.totals.excludedUnits}/${report.totals.originalUnits} hunks`);
106
117
  for (const file of report.files.filter((entry) => entry.uncoveredHunks.length > 0)) {
107
118
  lines.push(`${file.path}: ${file.uncoveredHunks.length} uncovered (${file.uncoveredHunks.join(', ')})`);
@@ -0,0 +1,90 @@
1
+ const { execFileSync } = require('child_process');
2
+ const { realpathSync } = require('fs');
3
+ const path = require('path');
4
+ const { parseNameStatusZ } = require('../out/gitComparison.js');
5
+
6
+ function git(root, args) {
7
+ return execFileSync('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 8 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'] });
8
+ }
9
+
10
+ function validatePath(value) {
11
+ if (typeof value !== 'string' || !value || value.includes('\0') || value.includes('\\') ||
12
+ path.posix.isAbsolute(value) || value.split('/').some(part => part === '..' || part === '.' || !part)) {
13
+ throw new Error('History requires a repository-relative file path.');
14
+ }
15
+ return value;
16
+ }
17
+
18
+ function validateCommit(root, value) {
19
+ if (typeof value !== 'string' || !/^[a-f0-9]{40,64}$/i.test(value)) {
20
+ throw new Error('History requires a full Git commit ID.');
21
+ }
22
+ const resolved = git(root, ['rev-parse', '--verify', `${value}^{commit}`]).trim();
23
+ if (resolved !== value.toLowerCase()) throw new Error('History commit could not be resolved.');
24
+ return resolved;
25
+ }
26
+
27
+ function createTourHistory(manifest) {
28
+ if (manifest.version !== 2) return null;
29
+ let root;
30
+ try {
31
+ root = realpathSync(manifest.repository.root);
32
+ if (realpathSync(git(root, ['rev-parse', '--show-toplevel']).trim()) !== root) throw new Error('Wrong repository root');
33
+ validateCommit(root, manifest.range.headOid);
34
+ validateCommit(root, manifest.range.mergeBaseOid);
35
+ } catch {
36
+ throw new Error('This v2 tour requires its originating Git repository. Restore the repository before presenting it.');
37
+ }
38
+ return {
39
+ list(input) {
40
+ const filePath = validatePath(input.path);
41
+ const commit = validateCommit(root, input.commit || manifest.range.headOid);
42
+ // Follow the file backwards from the requested revision, including deleted
43
+ // files and rename records; no working-tree file needs to exist.
44
+ const output = git(root, ['--literal-pathspecs', 'log', '--follow', '-M', '--max-count=250',
45
+ '--format=%x1e%H%x00%P%x00%s%x00%cI%x00', '--name-status', '-z', commit, '--', filePath]);
46
+ const entries = [];
47
+ let historicalPath = filePath;
48
+ for (const record of output.split('\x1e').slice(1)) {
49
+ const fields = record.split('\0');
50
+ const [oid, parents, summary, timestamp] = fields;
51
+ const changes = parseNameStatusZ(fields.slice(4).join('\0').replace(/^\0?\n/, ''));
52
+ const change = changes.find(item => item.path === historicalPath || item.previousPath === historicalPath) || changes[0];
53
+ if (!change) continue;
54
+ entries.push({ commit: oid, oid, parentCommit: parents.split(' ')[0] || null,
55
+ shortCommit: oid.slice(0, 7), summary, timestamp, path: change.path,
56
+ ...(change.previousPath ? { previousPath: change.previousPath } : {}) });
57
+ historicalPath = change.previousPath || change.path;
58
+ }
59
+ return { entries, selectedIndex: entries.length ? 0 : -1, selectedCommit: entries[0]?.commit || null,
60
+ ...(entries[0]?.commit !== commit ? { fallback: entries.length
61
+ ? 'Showing the closest preceding commit that changed this file.'
62
+ : 'No file history exists at this revision.' } : {}) };
63
+ },
64
+ diff(input) {
65
+ const filePath = validatePath(input.path);
66
+ const commit = validateCommit(root, input.commit);
67
+ const parents = git(root, ['show', '-s', '--format=%P', commit]).trim();
68
+ const parentCommit = parents.split(' ')[0] || null;
69
+ const changes = parseNameStatusZ(git(root, ['diff-tree', '--root', '--no-commit-id', '-r', '-M', '--name-status', '-z',
70
+ ...(parentCommit ? [parentCommit, commit] : [commit])]));
71
+ const change = changes.find(item => item.path === filePath || item.previousPath === filePath);
72
+ const rightPath = change?.path || filePath;
73
+ const leftPath = change?.previousPath || filePath;
74
+ function read(oid, name, absent) {
75
+ if (!oid || absent) return '';
76
+ const content = git(root, ['show', `${oid}:${name}`]);
77
+ if (content.includes('\0') || content.length > 2 * 1024 * 1024) throw new Error('History file is binary or too large to display.');
78
+ return content;
79
+ }
80
+ return { commit, parentCommit, path: rightPath,
81
+ ...(change?.previousPath ? { previousPath: change.previousPath } : {}),
82
+ leftContent: read(parentCommit, leftPath, change?.kind === 'added'),
83
+ rightContent: read(commit, rightPath, change?.kind === 'deleted'),
84
+ leftLabel: `${leftPath} @ ${parentCommit ? parentCommit.slice(0, 7) : 'empty'}`,
85
+ rightLabel: `${rightPath} @ ${commit.slice(0, 7)}` };
86
+ }
87
+ };
88
+ }
89
+
90
+ module.exports = { createTourHistory, validatePath };
@@ -22,6 +22,7 @@ _bygone() {
22
22
  '--git-diff:Compare two or more Git sources'
23
23
  '--branch-diff:Legacy alias for branch review'
24
24
  '--test:Open the built-in test comparison'
25
+ '--read-only:Open live Explore sources without allowing edits'
25
26
  '-C:Run as if Bygone started in this directory'
26
27
  '-h:Show command help'
27
28
  '--help:Show command help'
@@ -59,7 +60,7 @@ _bygone() {
59
60
  '--help:Show command help'
60
61
  )
61
62
  shells=('zsh:Z shell' 'bash:Bash' 'fish:Fish shell')
62
- tour_actions=('context:Build structured change evidence' 'coverage:Measure authored tour coverage' 'validate:Validate source and anchors' 'compile:Compile a portable manifest' 'schema:Print the source schema')
63
+ tour_actions=('context:Build structured change evidence' 'coverage:Measure authored tour coverage' 'validate:Validate source and anchors' 'compile:Compile a repository-bound manifest' 'schema:Print the source schema')
63
64
 
64
65
  if (( CURRENT == 2 )); then
65
66
  _describe 'bygone command' root_items
@@ -12,7 +12,7 @@ _bygone() {
12
12
  first="${COMP_WORDS[1]}"
13
13
 
14
14
  if (( COMP_CWORD == 1 )); then
15
- COMPREPLY=( $(compgen -W 'review completion present tour --diff --history --git-diff --branch-diff --test -C -h --help -v --version' -- "$cur") $(compgen -f -- "$cur") )
15
+ COMPREPLY=( $(compgen -W 'review completion present tour --diff --history --git-diff --branch-diff --test --read-only -C -h --help -v --version' -- "$cur") $(compgen -f -- "$cur") )
16
16
  compopt -o filenames 2>/dev/null || true
17
17
  return
18
18
  fi
@@ -13,6 +13,7 @@ complete -c bygone -n '__fish_use_subcommand' -l history -r -d 'Open Git history
13
13
  complete -c bygone -n '__fish_use_subcommand' -l git-diff -d 'Compare two or more Git sources'
14
14
  complete -c bygone -n '__fish_use_subcommand' -l branch-diff -d 'Legacy alias for branch review'
15
15
  complete -c bygone -n '__fish_use_subcommand' -l test -d 'Open the built-in test comparison'
16
+ complete -c bygone -n '__fish_use_subcommand' -l read-only -d 'Open live Explore sources without allowing edits'
16
17
  complete -c bygone -n '__fish_use_subcommand' -s C -r -d 'Run as if Bygone started in this directory'
17
18
  complete -c bygone -n '__fish_use_subcommand' -s h -l help -d 'Show command help'
18
19
  complete -c bygone -n '__fish_use_subcommand' -s v -l version -d 'Show the installed version'
@@ -8,9 +8,10 @@ suffix, and explicitly supplied files are validated by content rather than
8
8
  rejected by extension.
9
9
 
10
10
  An authored source is Git-backed, not portable: resolving refs and anchors
11
- requires the corresponding local repository and Git objects. A compiled
12
- `.tour.json` manifest contains the resolved source snapshots and is the
13
- portable artifact, although those snapshots may contain sensitive code.
11
+ requires the corresponding local repository and Git objects. Version 2
12
+ compiled manifests remain bound to that repository so the presenter can load
13
+ live file history. Version 1 manifests remain readable as legacy portable
14
+ artifacts, without the mode switcher.
14
15
 
15
16
  Packaged macOS builds register `.bygone` with Bygone, allowing a presentation
16
17
  inside its repository to open directly from Finder. Windows and Linux builds
@@ -31,7 +32,7 @@ The optional `range` pins the source's revisions for reproducibility; explicit
31
32
  command-line refs take precedence. Pinned OIDs still require their Git object
32
33
  database. The compiler resolves every anchor against the exact merge-base or
33
34
  head commit and writes the resulting commit IDs, line ranges, excerpts, file
34
- contents, and diffs into the portable JSON manifest served by the presenter.
35
+ contents, and diffs into the JSON manifest served by the presenter.
35
36
  Compilation fails if an anchor has no match or has multiple matches without an
36
37
  explicit `occurrence`.
37
38
 
@@ -42,7 +43,7 @@ once. When omitted, the presenter falls back to `title`, then a generic tour lab
42
43
  ## Structure
43
44
 
44
45
  ```yaml
45
- version: 1
46
+ version: 2
46
47
  title: A reviewer-facing title
47
48
  windowTitle: PR-1234
48
49
  sourceUrl: https://example.test/pull/123
@@ -82,8 +83,35 @@ chapters:
82
83
  body: This exact line establishes the ordering guarantee.
83
84
  focus: durable-decision
84
85
  connection: contract-to-write
86
+ requirement:
87
+ id: R1
88
+ text: Decisions must be durable before any side effect runs.
89
+ status: fulfilled
90
+ source: tracker:PROJ-123
91
+ confidence: high
85
92
  ```
86
93
 
94
+ ## Requirements
95
+
96
+ A walkthrough step may carry an optional `requirement` that names the stated
97
+ requirement the focused code fulfills. It turns a walkthrough into a
98
+ requirements tour: each step answers which requirement it satisfies, where that
99
+ requirement was stated, and how confident the author is in the mapping.
100
+
101
+ | Field | Required | Value |
102
+ |-------|----------|-------|
103
+ | `id` | yes | Short requirement label, such as `R1` |
104
+ | `text` | yes | The requirement statement in its original wording |
105
+ | `status` | no | `fulfilled` or `gap` |
106
+ | `source` | no | Where the requirement was stated, such as `pr-description` or `tracker:PROJ-123` |
107
+ | `confidence` | no | `high`, `medium`, or `low` |
108
+
109
+ The presenter shows a chip with the id and status beside the step title, the
110
+ requirement text next to it, and the source and confidence on a secondary line.
111
+ Steps without a requirement render unchanged. A `gap` status records a
112
+ requirement the author judged unmet at the anchored code; it does not fail
113
+ validation, since the judgment is narrative rather than mechanical.
114
+
87
115
  Only the active step's connection is shown. This keeps relationships useful without adding a permanent second layer of curves to Bygone's diff view. The Tour rail contains only authored scenes; the adjacent Files rail independently lists the complete change set. Browsing a file does not move the narrative, and “Return to tour” restores the file and annotation focused by the current scene.
88
116
 
89
117
  The presenter search (`Cmd/Ctrl+Shift+F`) can search narrative and code together
@@ -92,12 +120,24 @@ results search the compiled base and head snapshots, open the exact file and
92
120
  side, and preserve **Return to tour** so exploration does not lose authored
93
121
  context.
94
122
 
123
+ Version 2 tours show one mode control for jumping directly among the authored
124
+ explanation, real revisions, the final endpoint diff, and live Git history.
125
+ Only modes at or above the tour's maximum authored depth appear. A two-panel
126
+ stack therefore collapses into Final diff rather than creating a duplicate
127
+ Revisions mode. Each mode retains its own location; switching back without
128
+ navigating restores that location exactly.
129
+
95
130
  Use a walkthrough by default. Use a [stacked-diff example](../examples/stacked-diff.bygone)
96
131
  only when every panel is a real selected Git revision. Use a
97
132
  [deconstructed-diff example](../examples/deconstructed-diff.bygone) when
98
133
  the teaching order is clearer than the real commit history; its cumulative
99
134
  panels are synthetic explanation stages and must never be described as
100
- commits. Every changed hunk must be assigned once or explicitly excluded.
135
+ commits. In version 2, every deconstructed scene must also provide the regular
136
+ endpoint tour's `steps` and an explicit `stack` of the real revisions with the
137
+ same base and final endpoints. Final diff presents those authored walkthrough
138
+ steps, while Explanation stages presents the synthetic reconstruction. Bygone
139
+ does not infer the stack from Git history. Every changed hunk must be assigned
140
+ once or explicitly excluded.
101
141
 
102
142
  See [Bygone's self-referencing history tour](../examples/bygone-history.bygone) for a complete walkthrough that pins and explains the commit where branch review was introduced.
103
143
 
package/docs/explore.md CHANGED
@@ -20,15 +20,35 @@ Writable filesystem panels are labeled **Writable file**. Git revisions,
20
20
  history, tours, and supplied snapshots are labeled **Read-only snapshot**.
21
21
  Replace and save operations are unavailable for read-only content.
22
22
 
23
+ Pass `--read-only` anywhere in a desktop launch command to keep otherwise-live
24
+ files visible and refreshable without allowing edits or saves. For example,
25
+ `bygone --read-only --diff old.txt current.txt` and
26
+ `bygone -C repo --git-diff HEAD WORKTREE --read-only` label their live sources
27
+ **Read-only file**. The restriction remains in effect when the session refreshes;
28
+ it does not change new comparisons opened later from the app menus.
29
+
23
30
  ## Ask Git questions
24
31
 
25
32
  - **File → View File or Directory History…** follows one path through commits and
26
33
  supported index/worktree states.
27
34
  - **Git → Review Branch Change…** accepts a head and optional base ref, compares the
28
- head with its merge base, and shows the complete changed-file inventory.
35
+ head with its merge base, and opens a deterministic overview before the complete
36
+ changed-file inventory. **Start here** identifies one production entry point when
37
+ the range supports a reliable choice, **Pay attention** keeps the first-pass
38
+ production set small, and **Usually skip** collapses mechanical and background
39
+ files without removing them. Use **Files** to reach the full directory tree.
29
40
  - **Git → Compare Revisions…** accepts two or more refs, including `INDEX` and
30
41
  `WORKTREE`, matching CLI `--git-diff`.
31
42
 
43
+ In a revision comparison, committed refs and `INDEX` are read-only snapshots.
44
+ `WORKTREE` (also accepted as `WORKDIR` or `WORKINGTREE`) is the live repository
45
+ tree and is writable unless the launch uses `--read-only`. For example,
46
+ `bygone -C repo --git-diff HEAD WORKTREE`
47
+ opens the HEAD snapshot on the left and editable worktree files on the right;
48
+ saving updates the repository file, including recreating a deleted file. The
49
+ directory inventory follows `git ls-files -co --exclude-standard`, so ignored
50
+ files and `.git` internals are not included.
51
+
32
52
  Branch exploration uses committed content. Dirty index and worktree changes
33
53
  are reported separately and are never silently folded into a commit snapshot.
34
54
 
package/docs/present.md CHANGED
@@ -6,7 +6,10 @@ source evidence while keeping the complete changed-file set available.
6
6
  ## Generated and authored tours
7
7
 
8
8
  - **Present Current Branch** or `bygone present` generates a deterministic
9
- reading order for the committed merge-base-to-tip range.
9
+ reading order for the committed merge-base-to-tip range. It begins with a
10
+ change overview that reports range size, recommends a **Start here** file when
11
+ the evidence supports one, and separates **Pay attention** from **Usually
12
+ skip** first-pass reading hints. Next advances to the recommended file.
10
13
  - **Open Authored Tour…** or `bygone present --tour path.bygone` opens a
11
14
  narrative whose anchors have been compiled to exact source locations.
12
15
 
@@ -14,6 +17,11 @@ The Files rail is independent of the narrative. Browsing another file keeps
14
17
  the active tour anchor visible and offers **Return to Tour** to restore the
15
18
  scene's intended focus.
16
19
 
20
+ Generated overview ranking is deterministic for the resolved Git object IDs.
21
+ It never hides files and does not invent an entry point for a test-only,
22
+ lockfile-only, binary, or generated-only change. Authored tours keep their
23
+ authored first scene and order.
24
+
17
25
  ## Listen to a tour
18
26
 
19
27
  Use **Listen** in the narrative header or **Present → Listen to Tour** in the
@@ -30,7 +30,7 @@ renderer does not make every workflow appropriate in every host.
30
30
  | Compare directories | File → Compare Directories; drag/drop; two or more directory arguments | Directory comparison | Directory rows are read-only; writable file drill-down depends on source/review state | Core | directory smoke and directory-diff tests |
31
31
  | Follow one file or directory through Git | File → View File or Directory History; one path; `--history` | Git commits plus supported index/worktree states | Commits are read-only; worktree/index provenance is explicit | Core | Git history and directory-history tests |
32
32
  | Compare named revisions | Git → Compare Revisions; `bygone --git-diff <refs…>` | Git blobs/revisions | Read-only snapshots | Core | Git comparison tests and desktop menu contract |
33
- | Explore a branch change | Git → Review Branch Change; `bygone review` | Merge-base-to-tip branch inventory for a selected head and optional base | Read-only review snapshots; dirty state is reported, not silently included | Core | branch comparison, review-path, and desktop menu tests |
33
+ | Explore a branch change | Git → Review Branch Change; `bygone review` | Deterministic start-here overview plus the complete merge-base-to-tip inventory | Read-only review snapshots; attention is a reading-order hint, viewed state remains session-local, and dirty state is reported rather than included | Core | attention-model, branch comparison, review-path, and desktop menu tests |
34
34
  | Search comparison code | Edit → Find or Search Comparison | Active, visible, all loaded panels, unopened changed text snapshots, or one file's Git history | Search is read-only; history distinguishes content occurrence from introduction/removal; Replace requires the active writable model | Core | find, comparison-search, change-set-search, and history-search tests |
35
35
  | Search or safely replace in a filesystem tree | Edit → Search in Files; explicit directory root | System ripgrep 14+ with include/exclude, hidden, ignore, and result-limit controls | Search is read-only and cancellable; completed case-sensitive literal searches can build an exclusion-aware, revalidated, atomic replacement preview with guarded undo | Optional system capability | repository search/replace contracts, benchmark, and host-protocol tests |
36
36
  | Move through a multi-panel comparison | Strip buttons, panel headers, gutters, wheel, or `Alt+Arrow` | Active panel and adjacent pair | Does not change source identity | Core | focused-strip controller and standalone smoke tests |
@@ -46,8 +46,9 @@ appear only when Electron is not packaged.
46
46
 
47
47
  | User question | Discovery and launch | Identity/provenance | Mutability | Maturity | Verification |
48
48
  | --- | --- | --- | --- | --- | --- |
49
- | Explain a branch automatically | Present → Present Current Branch or Present Branch or Ref; `bygone present` | Committed merge-base-to-tip range for the current or selected head/base | Read-only | Core | presentation launch, generated-tour, and desktop menu tests |
49
+ | Explain a branch automatically | Present → Present Current Branch or Present Branch or Ref; `bygone present` | Committed merge-base-to-tip range with a deterministic overview first scene and complete Files rail | Read-only; authored tours retain their authored order | Core | attention-model, presentation launch, generated-tour, and desktop menu tests |
50
50
  | Open an authored walkthrough | Present → Open Authored Tour; `bygone present --tour`; VS Code hand-off | Manifest range, scene, step, exact source anchor | Read-only | Core | reproducible example and tour validation tests |
51
+ | Assess authored tour coverage | Coverage indicators in the Present sidebar | Distinct hunks referenced by authored walkthrough anchors; v2 Final walkthrough coverage remains separate from deconstructed Explanation assignment completeness | Read-only authoring diagnostics; generated tours have no authored coverage metric | Advanced authoring | v1/v2 coverage and presenter-host tests |
51
52
  | Listen to a generated or authored tour | Narrative Listen controls; Present → Listen to Tour | Existing manifest narrative segmented locally by scene/step and visible sentence | Read-only device TTS; voice/rate preferences remain local; continuous playback follows tour navigation and direct exploration pauses it | Core | narration model/controller and presenter-host tests |
52
53
  | Explain a real stack | `stacked-diff` scene | Exact real Git revisions per panel | Read-only | Advanced | `examples/stacked-diff.bygone` |
53
54
  | Explain a change in conceptual stages | `deconstructed-diff` scene | Explicitly labeled synthetic explanation stages backed by exact hunk IDs | Read-only | Advanced/experimental authoring | `examples/deconstructed-diff.bygone` |
@@ -78,22 +79,25 @@ VS Code's native Search rather than another bundled ripgrep process.
78
79
 
79
80
  | Surface | Purpose | Mutability/maturity |
80
81
  | --- | --- | --- |
81
- | `bygone`, paths, `--diff`, `--history`, `--git-diff`, `review` | Launch Explore sessions | Core launcher; desktop owns writes |
82
+ | `bygone`, paths, `--diff`, `--history`, `--git-diff`, `--read-only`, `review` | Launch Explore sessions | Core launcher; desktop owns writes; `--read-only` is a persisted capability ceiling for that launch |
82
83
  | `present` and `present --tour` | Launch Present sessions | Core, read-only presentation |
83
84
  | `tour context` | Produce a bounded provider-neutral change dossier | Authoring support, read-only |
84
- | `tour validate`, `compile`, `schema`, and coverage options | Validate exact evidence and build portable manifests | Advanced authoring, read-only except explicit output files |
85
+ | `tour validate`, `compile`, `schema`, and coverage options | Validate exact evidence and build repository-bound manifests | Advanced authoring, read-only except explicit output files |
85
86
  | `completion` | Generate shell completions from the shared command specification | Supporting tooling |
86
87
 
87
88
  ## Provenance and mutability vocabulary
88
89
 
89
90
  Use these labels consistently:
90
91
 
91
- - **Writable file:** filesystem/worktree content participating in dirty, undo,
92
+ - **Writable file:** filesystem content and a live `WORKTREE` column participating in dirty, undo,
92
93
  save, reload, watcher, and refresh behavior.
94
+ - **Read-only file:** live filesystem content intentionally opened with editing
95
+ disabled; external changes and explicit session refresh remain available.
93
96
  - **Read-only snapshot:** committed Git, history, tour, or supplied content that
94
97
  cannot be replaced or saved over a source file.
95
98
  - **INDEX** and **WORKTREE:** repository work states; do not describe them as
96
- commits.
99
+ commits. `INDEX` is a read-only snapshot; `WORKTREE` is live and writable in
100
+ revision comparisons unless the launch uses `--read-only`.
97
101
  - **Explanation stage:** synthetic deconstructed content; never imply that it
98
102
  is a real commit.
99
103
  - **Real revision:** a panel resolved from an exact Git object identity.