@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 +10 -0
- package/README.md +2 -2
- package/cli/commandSpec.js +12 -2
- package/cli/completions.js +1 -1
- package/cli/present.js +42 -1
- package/cli/tour.js +13 -2
- package/cli/tourHistory.js +90 -0
- package/completions/_bygone +2 -1
- package/completions/bygone +1 -1
- package/completions/bygone.fish +1 -0
- package/docs/change-tour-format.md +46 -6
- package/docs/explore.md +21 -1
- package/docs/present.md +9 -1
- package/docs/product-surface.md +10 -6
- package/docs/releasing.md +25 -29
- package/media/diff.worker.js +3 -3
- package/media/webview.css +1 -1
- package/media/webview.js +172 -172
- package/out/changeTour.js +953 -410
- package/out/gitComparison.js +354 -0
- package/out/standalone-main.js +7892 -2495
- package/out/tourCoverage.js +992 -402
- package/package.json +1 -1
- package/schemas/change-tour-source.schema.json +58 -3
- package/skills/pr-tour-guide/SKILL.md +16 -7
- package/standalone/index.html +5 -0
- package/web/index.html +14 -0
- package/web/presenter.css +101 -0
- package/web/web-host.js +5 -5
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
|
|
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,
|
|
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
|
|
package/cli/commandSpec.js
CHANGED
|
@@ -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
|
|
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
|
|
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.'
|
package/cli/completions.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|
package/completions/_bygone
CHANGED
|
@@ -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
|
|
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
|
package/completions/bygone
CHANGED
|
@@ -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
|
package/completions/bygone.fish
CHANGED
|
@@ -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.
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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:
|
|
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.
|
|
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
|
|
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
|
package/docs/product-surface.md
CHANGED
|
@@ -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` |
|
|
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
|
|
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
|
|
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
|
|
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.
|