@davmash/bygone 0.10.1 → 0.11.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.
- package/CHANGELOG.md +25 -0
- package/README.md +64 -0
- package/bin/bygone.js +18 -1
- package/cli/commandSpec.js +22 -2
- package/cli/completions.js +30 -8
- package/cli/correspondence.js +71 -0
- package/cli/dependencies.js +62 -0
- package/cli/evidence.js +67 -0
- package/cli/present.js +61 -16
- package/cli/tour.js +18 -1
- package/cli/tourExport.js +1 -1
- package/completions/_bygone +15 -3
- package/completions/bygone +13 -5
- package/completions/bygone.fish +9 -0
- package/docs/change-tour-format.md +60 -6
- package/docs/correspondence.md +259 -0
- package/docs/dependencies.md +94 -0
- package/docs/evidence-review.md +163 -0
- package/docs/generating-change-tours.md +3 -0
- package/docs/present.md +3 -0
- package/docs/product-surface.md +2 -1
- package/docs/releasing.md +11 -0
- package/docs/vscode-integration.md +183 -0
- package/media/diff.worker.js +10 -9
- package/media/webview.css +1 -1
- package/media/webview.js +167 -167
- package/out/changeCorrespondence.js +739 -0
- package/out/changeTour.js +1366 -967
- package/out/dependencyGraph.js +213939 -0
- package/out/gitComparison.js +4 -0
- package/out/requirements.js +427 -0
- package/out/reviewDependencies.js +669 -0
- package/out/reviewWorkspace.js +1129 -0
- package/out/standalone-main.js +3382 -1341
- package/out/tourCoverage.js +1852 -1448
- package/package.json +2 -2
- package/schemas/change-tour-source.schema.json +48 -3
- package/web/export-runtime.json +6 -6
- package/web/index.html +13 -0
- package/web/presenter.css +81 -0
- package/web/web-host.js +23 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.1
|
|
4
|
+
|
|
5
|
+
- Added pinned evidence reviews in the CLI and VS Code, with explicit refresh, source validation, and return-to-review navigation
|
|
6
|
+
- Connected evidence ranges to synchronized working-file and symbol editing while preserving historical review snapshots
|
|
7
|
+
- Added dependency-guided review navigation and improved modern TypeScript syntax coverage, metadata resolution, and report validation
|
|
8
|
+
|
|
9
|
+
## 0.11.0
|
|
10
|
+
|
|
11
|
+
- Added experimental JS/TS and Python symbol evidence with declaration diffs, symbol trees, analysis diagnostics, and shared Files/Symbols navigation
|
|
12
|
+
- Added pinned symbol tour stops and full-file context in live presentations and portable exports
|
|
13
|
+
- Added the local Strand declaration matcher for JS/TS, Python, Java, Go, Rust, C#, Ruby, PHP, C, Lua, and experimental Mojo, with bounded analysis and explicit ambiguity reporting
|
|
14
|
+
- Kept Java and RefactoringMiner optional: exact-only generation records partial coverage, and Strand provides local inference without a JVM
|
|
15
|
+
- Added opt-in VS Code active-editor following with a live HEAD-to-buffer diff, Pin/Resume/Refresh controls, and safe handoffs to history and manual comparison
|
|
16
|
+
- Fixed following for macOS directory aliases and files at the 5,000-line limit, with regression coverage on current and minimum-supported VS Code
|
|
17
|
+
- Hardened correspondence artifact validation, parser budgets, and symbol owner matching
|
|
18
|
+
- Updated Electron, DOMPurify, fast-uri, and packaging dependencies to resolve security advisories
|
|
19
|
+
|
|
20
|
+
macOS desktop downloads are unsigned and not notarized. If Gatekeeper reports
|
|
21
|
+
the app as damaged, run `xattr -cr /Applications/Bygone.app`, then reopen it.
|
|
22
|
+
|
|
23
|
+
## 0.10.2
|
|
24
|
+
|
|
25
|
+
- Prioritized matching named lines before brackets to preserve block correspondences across nesting and indentation changes
|
|
26
|
+
- Aligned changed indentation highlights to the start of each line while preserving highlights on edited text
|
|
27
|
+
|
|
3
28
|
## 0.10.1
|
|
4
29
|
|
|
5
30
|
- Fixed desktop tours showing unstyled HTML after temporary asset cleanup during long-running app sessions
|
package/README.md
CHANGED
|
@@ -214,6 +214,8 @@ Contributors should consult the internal [product surface overview](./docs/produ
|
|
|
214
214
|
|
|
215
215
|
`bygone tour context [<head>] --base <base>` produces a provider-neutral change dossier with bounded patches, changed ranges, file roles, symbol hints, commits, renames, and explicit binary omissions for an LLM to consume.
|
|
216
216
|
|
|
217
|
+
`bygone dependencies [<head>] --base <base>` compares static JS/TS module dependencies, labels initialization versus deferred use, and lists unchanged consumers of changed modules. Add `--json` for exact source evidence, or use `bygone tour context --base <base> --dependencies` to include it in a change dossier. See [dependency evidence](docs/dependencies.md) for resolution rules and coverage limits. These analysis commands run through the npm/source Node CLI.
|
|
218
|
+
|
|
217
219
|
The desktop app also includes `Help -> Install Command Line Tools...` for non-npm installs. Homebrew cask installs can provide the `bygone` command automatically.
|
|
218
220
|
|
|
219
221
|
### macOS says the app is damaged
|
|
@@ -352,6 +354,10 @@ The wrapper scripts are:
|
|
|
352
354
|
|
|
353
355
|
## Commands
|
|
354
356
|
|
|
357
|
+
- `Bygone: Follow Active Editor`
|
|
358
|
+
- `Bygone: Pin Following File`
|
|
359
|
+
- `Bygone: Resume Following`
|
|
360
|
+
- `Bygone: Refresh Following Comparison`
|
|
355
361
|
- `Bygone: Compare Active File With…`
|
|
356
362
|
- `Bygone: Compare Selected Files`
|
|
357
363
|
- `Bygone: Compare with Selected`
|
|
@@ -370,6 +376,64 @@ panes edit through VS Code documents, so changes participate in dirty state,
|
|
|
370
376
|
undo, save, and external-document updates. Historical and non-local inputs are
|
|
371
377
|
read-only. Desktop hand-offs are unavailable in remote extension hosts.
|
|
372
378
|
|
|
379
|
+
## Follow the active VS Code editor
|
|
380
|
+
|
|
381
|
+
Run **Bygone: Follow Active Editor** to open one comparison beside your source
|
|
382
|
+
editor without moving keyboard focus. Switching source files updates that pane.
|
|
383
|
+
The left side is HEAD; the right side is the VS Code document, including unsaved
|
|
384
|
+
edits. Edit either the source pane or Bygone's working pane: both update the same
|
|
385
|
+
document. Save, Undo and Redo in Bygone use that document's save and edit history.
|
|
386
|
+
Typing neither saves the file nor stages it.
|
|
387
|
+
|
|
388
|
+
Source Control offers **Open Staged Changes in Bygone** (HEAD → index) and
|
|
389
|
+
**Open Working Changes in Bygone** (index → working buffer), in the file's inline
|
|
390
|
+
and context menus. Staged views exclude unsaved edits and are read-only. Native
|
|
391
|
+
stage, unstage, discard, commit and checkout operations refresh live comparisons.
|
|
392
|
+
The selected policy stays in place when a file leaves its SCM group; an empty
|
|
393
|
+
comparison shows that there are now no changes. Enable `bygone.followScmDiffs`
|
|
394
|
+
to follow supported native Git diff tabs during a following session. This does
|
|
395
|
+
not change the SCM tree's default click behavior.
|
|
396
|
+
|
|
397
|
+
Use **Bygone: Compare Symbol in Bygone**, or enable `bygone.followSymbol` to follow
|
|
398
|
+
Outline, Go to Symbol and cursor selections. Symbol focus preserves the selected
|
|
399
|
+
comparison and uses the existing Strand matching engine. JavaScript and TypeScript
|
|
400
|
+
are supported within the selected file pair, including Git-reported renames.
|
|
401
|
+
Dirty-buffer selections in staged views require a verified mapping to the index.
|
|
402
|
+
Unsupported or ambiguous selections show a reason and retain file context or a
|
|
403
|
+
known one-sided symbol. **Show File Context** returns to the whole comparison;
|
|
404
|
+
**Reveal Source** opens the working document or the exact read-only snapshot.
|
|
405
|
+
|
|
406
|
+
Working symbol slices are editable. Edits before a declaration move its tracked
|
|
407
|
+
range; temporary syntax errors keep its editing range usable. Replacing or deleting
|
|
408
|
+
the declaration externally returns to file context. If a concurrent edit makes a
|
|
409
|
+
pending write stale, Bygone preserves the attempted text in an untitled recovery
|
|
410
|
+
document instead of overwriting the source. Pending drafts are also recovered
|
|
411
|
+
when the panel closes or the extension starts again after a reload.
|
|
412
|
+
|
|
413
|
+
**Pin** keeps the target, baseline and symbol focus. A pinned working endpoint
|
|
414
|
+
continues to reflect edits; a pinned staged view holds both snapshots. Git changes
|
|
415
|
+
show a notice until **Resume Following** resolves current endpoints. **Refresh**
|
|
416
|
+
updates live comparisons; pinned snapshots remain fixed. Source navigation within
|
|
417
|
+
the selected file preserves SCM policy; a different ordinary file selects HEAD →
|
|
418
|
+
working. **View File History** and **Manual Compare…** pause following and open
|
|
419
|
+
separate views. A restored following panel starts paused.
|
|
420
|
+
|
|
421
|
+
Labels identify the repository, paths, comparison policy and saved/unsaved state.
|
|
422
|
+
New/deleted files and unborn HEAD use explicit absent endpoints. Following supports
|
|
423
|
+
trusted local Git worktrees, including linked worktrees and multiple repositories.
|
|
424
|
+
Each selected endpoint must be text, at most 128 KiB and 5,000 lines; Git snapshots
|
|
425
|
+
must be UTF-8. Conflicts, unsupported diff inputs, symlinks, binary, remote,
|
|
426
|
+
virtual and notebook inputs show an explicit unavailable state. Git failures clear
|
|
427
|
+
the previous comparison. Arbitrary cross-file symbol moves and hunk staging inside
|
|
428
|
+
Bygone are outside this integration.
|
|
429
|
+
|
|
430
|
+
Use **Bygone: Open Evidence Review** to inspect pinned requirement evidence, edit
|
|
431
|
+
the working counterpart, and return to the original review. See the
|
|
432
|
+
[evidence workflow and PR Reviewer bridge](docs/evidence-review.md).
|
|
433
|
+
|
|
434
|
+
See the [integration contract and verification record](docs/vscode-integration.md)
|
|
435
|
+
for snapshot/edit safeguards, analysis budgets and reproducible Extension Host tests.
|
|
436
|
+
|
|
373
437
|
## Limitations
|
|
374
438
|
|
|
375
439
|
- Bygone is intentionally diff-focused; merge tooling is not exposed as a product feature.
|
package/bin/bygone.js
CHANGED
|
@@ -40,7 +40,24 @@ if (tokenMatches('completion', args[0])) {
|
|
|
40
40
|
process.exit(0);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
if (
|
|
43
|
+
if (args[0] === 'evidence') {
|
|
44
|
+
try { require('../cli/evidence').runEvidenceCommand(args.slice(1), cliCwd); }
|
|
45
|
+
catch (error) { process.stderr.write(`Could not process evidence: ${error.message}\n`); process.exitCode = 1; }
|
|
46
|
+
} else if (args[0] === 'dependencies') {
|
|
47
|
+
try {
|
|
48
|
+
require('../cli/dependencies').runDependenciesCommand(args.slice(1), cliCwd);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
process.stderr.write(`Could not analyze dependencies: ${error.message}\n`);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
}
|
|
53
|
+
} else if (args[0] === 'correspondence') {
|
|
54
|
+
try {
|
|
55
|
+
require('../cli/correspondence').importCorrespondenceCommand(args.slice(1), cliCwd);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
process.stderr.write(`Could not import correspondence: ${error.message}\n`);
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
} else if (tokenMatches('tourCommand', args[0])) {
|
|
44
61
|
try {
|
|
45
62
|
runTourCommand(args.slice(1), cliCwd, packageRoot);
|
|
46
63
|
} catch (error) {
|
package/cli/commandSpec.js
CHANGED
|
@@ -13,10 +13,13 @@ const CLI_SPEC = Object.freeze({
|
|
|
13
13
|
'bygone --read-only <desktop-arguments...>',
|
|
14
14
|
'bygone review [<head>] [--base <base>]',
|
|
15
15
|
'bygone -C <directory> <command-or-path> [...]',
|
|
16
|
-
'bygone present [<head>] [--base <base>] [--tour <file.bygone>]',
|
|
16
|
+
'bygone present [<head>] [--base <base>] [--tour <file.bygone>] [--correspondence <artifact.json>]',
|
|
17
17
|
'bygone tour validate <file.bygone> [--json]',
|
|
18
18
|
'bygone tour compile <file.bygone> [--output <tour.json>]',
|
|
19
|
-
'bygone tour context [<head>] [--base <base>] [--output <context.json>]',
|
|
19
|
+
'bygone tour context [<head>] [--base <base>] [--output <context.json>] [--correspondence <artifact.json>] [--dependencies]',
|
|
20
|
+
'bygone evidence <inspect|import|refresh|tour> <review.json> [--head <revision>] [--output <new-file>]',
|
|
21
|
+
'bygone dependencies [<head>] [--base <base>] [--json] [--output <graph.json>]',
|
|
22
|
+
'bygone correspondence import <report.json> [<head>] [--base <base>] [--case <id>] --output <artifact.json>',
|
|
20
23
|
'bygone tour coverage <file.bygone> [--json] [--minimum-coverage <0-100>]',
|
|
21
24
|
'bygone tour export <file.bygone> --output <tour.html> [--profile minimal|full] [--runtime embedded|cdn]',
|
|
22
25
|
'bygone <bygone://open/v1?...>',
|
|
@@ -26,6 +29,7 @@ const CLI_SPEC = Object.freeze({
|
|
|
26
29
|
'bygone --test'
|
|
27
30
|
],
|
|
28
31
|
entries: [
|
|
32
|
+
{ id: 'evidence', kind: 'command', tokens: ['evidence'], description: 'Inspect, import, refresh, or present pinned review evidence', argument: 'path' },
|
|
29
33
|
{
|
|
30
34
|
id: 'review',
|
|
31
35
|
kind: 'command',
|
|
@@ -33,6 +37,18 @@ const CLI_SPEC = Object.freeze({
|
|
|
33
37
|
description: 'Review a committed branch against its merge base',
|
|
34
38
|
argument: 'git-ref'
|
|
35
39
|
},
|
|
40
|
+
{
|
|
41
|
+
id: 'dependencies', kind: 'command', tokens: ['dependencies'],
|
|
42
|
+
description: 'Compare static JS/TS module dependencies at committed revisions', argument: 'git-ref'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'dependencyContext', kind: 'option', tokens: ['--dependencies'],
|
|
46
|
+
description: 'Include static module dependency evidence in tour context', argument: 'none'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'correspondence', kind: 'command', tokens: ['correspondence'],
|
|
50
|
+
description: 'Import externally produced symbol correspondence evidence', argument: 'path'
|
|
51
|
+
},
|
|
36
52
|
{
|
|
37
53
|
id: 'completion',
|
|
38
54
|
kind: 'command',
|
|
@@ -110,6 +126,10 @@ const CLI_SPEC = Object.freeze({
|
|
|
110
126
|
description: 'Use an authored .bygone presentation',
|
|
111
127
|
argument: 'path'
|
|
112
128
|
},
|
|
129
|
+
{
|
|
130
|
+
id: 'correspondenceEvidence', kind: 'option', tokens: ['--correspondence'],
|
|
131
|
+
description: 'Use pinned symbol evidence in present or tour context', argument: 'path'
|
|
132
|
+
},
|
|
113
133
|
{
|
|
114
134
|
id: 'readOnly',
|
|
115
135
|
kind: 'global',
|
package/cli/completions.js
CHANGED
|
@@ -35,7 +35,7 @@ function generateZshCompletion() {
|
|
|
35
35
|
const reviewOptions = entriesByIds(['base', 'help'])
|
|
36
36
|
.flatMap((entry) => entry.tokens.map((token) => ` '${token}:${escapeSingleQuoted(entry.description)}'`))
|
|
37
37
|
.join('\n');
|
|
38
|
-
const presentOptions = entriesByIds(['base', 'tour', 'help'])
|
|
38
|
+
const presentOptions = entriesByIds(['base', 'tour', 'correspondenceEvidence', 'help'])
|
|
39
39
|
.flatMap((entry) => entry.tokens.map((token) => ` '${token}:${escapeSingleQuoted(entry.description)}'`))
|
|
40
40
|
.join('\n');
|
|
41
41
|
const legacyOptions = entriesByIds(['branch', 'base', 'help'])
|
|
@@ -93,16 +93,24 @@ ${historyOptions}
|
|
|
93
93
|
elif [[ "$words[3]" == "context" ]]; then
|
|
94
94
|
if [[ "$previous" == ${shellAlternation(tokensFor('base'))} ]]; then
|
|
95
95
|
_bygone_git_refs
|
|
96
|
-
elif [[ "$previous" == "--output" || "$previous" == "-o" ]]; then
|
|
96
|
+
elif [[ "$previous" == "--output" || "$previous" == "-o" || "$previous" == "--correspondence" ]]; then
|
|
97
97
|
_files
|
|
98
98
|
else
|
|
99
|
-
compadd -- --base --output -o --max-patch-bytes --max-total-patch-bytes
|
|
99
|
+
compadd -- --base --output -o --max-patch-bytes --max-total-patch-bytes --correspondence --dependencies
|
|
100
100
|
_bygone_git_refs
|
|
101
101
|
fi
|
|
102
102
|
elif [[ "$words[3]" == "coverage" || "$words[3]" == "validate" || "$words[3]" == "compile" ]]; then
|
|
103
103
|
_files -g '*.bygone' -g '*.bygone.yaml'
|
|
104
104
|
fi
|
|
105
105
|
;;
|
|
106
|
+
dependencies)
|
|
107
|
+
if [[ "$previous" == "--output" || "$previous" == "-o" ]]; then
|
|
108
|
+
_files
|
|
109
|
+
else
|
|
110
|
+
compadd -- --base --json --output -o
|
|
111
|
+
_bygone_git_refs
|
|
112
|
+
fi
|
|
113
|
+
;;
|
|
106
114
|
review)
|
|
107
115
|
if [[ "$previous" == ${shellAlternation(tokensFor('base'))} ]]; then
|
|
108
116
|
_bygone_git_refs
|
|
@@ -112,7 +120,7 @@ ${historyOptions}
|
|
|
112
120
|
fi
|
|
113
121
|
;;
|
|
114
122
|
present)
|
|
115
|
-
if [[ "$previous" == ${shellAlternation(tokensFor('tour'))} ]]; then
|
|
123
|
+
if [[ "$previous" == ${shellAlternation(tokensFor('tour'))} || "$previous" == "--correspondence" ]]; then
|
|
116
124
|
_files -g '*.bygone' -g '*.bygone.yaml'
|
|
117
125
|
elif [[ "$previous" == ${shellAlternation(tokensFor('base'))} ]]; then
|
|
118
126
|
_bygone_git_refs
|
|
@@ -152,7 +160,7 @@ compdef _bygone bygone
|
|
|
152
160
|
function generateBashCompletion() {
|
|
153
161
|
const rootTokens = completionEntries().flatMap((entry) => entry.tokens).join(' ');
|
|
154
162
|
const reviewTokens = entriesByIds(['base', 'help']).flatMap((entry) => entry.tokens).join(' ');
|
|
155
|
-
const presentTokens = entriesByIds(['base', 'tour', 'help']).flatMap((entry) => entry.tokens).join(' ');
|
|
163
|
+
const presentTokens = entriesByIds(['base', 'tour', 'correspondenceEvidence', 'help']).flatMap((entry) => entry.tokens).join(' ');
|
|
156
164
|
const legacyTokens = entriesByIds(['branch', 'base', 'help']).flatMap((entry) => entry.tokens).join(' ');
|
|
157
165
|
const historyTokens = entriesByIds(['includeStaged', 'help']).flatMap((entry) => entry.tokens).join(' ');
|
|
158
166
|
const baseTokens = tokensFor('base').join('|');
|
|
@@ -188,17 +196,25 @@ _bygone() {
|
|
|
188
196
|
refs="$(_bygone_git_refs)"
|
|
189
197
|
if [[ "$prev" =~ ^(${baseTokens})$ ]]; then
|
|
190
198
|
COMPREPLY=( $(compgen -W "$refs" -- "$cur") )
|
|
191
|
-
elif [[ "$prev" == "--output" || "$prev" == "-o" ]]; then
|
|
199
|
+
elif [[ "$prev" == "--output" || "$prev" == "-o" || "$prev" == "--correspondence" ]]; then
|
|
192
200
|
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
193
201
|
compopt -o filenames 2>/dev/null || true
|
|
194
202
|
else
|
|
195
|
-
COMPREPLY=( $(compgen -W '--base --output -o --max-patch-bytes --max-total-patch-bytes '"$refs" -- "$cur") )
|
|
203
|
+
COMPREPLY=( $(compgen -W '--base --output -o --max-patch-bytes --max-total-patch-bytes --correspondence --dependencies '"$refs" -- "$cur") )
|
|
196
204
|
fi
|
|
197
205
|
else
|
|
198
206
|
COMPREPLY=( $(compgen -W '--json --output -o' -- "$cur") $(compgen -f -- "$cur") )
|
|
199
207
|
compopt -o filenames 2>/dev/null || true
|
|
200
208
|
fi
|
|
201
209
|
;;
|
|
210
|
+
dependencies)
|
|
211
|
+
if [[ "$prev" == "--output" || "$prev" == "-o" ]]; then
|
|
212
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
213
|
+
compopt -o filenames 2>/dev/null || true
|
|
214
|
+
else
|
|
215
|
+
COMPREPLY=( $(compgen -W '--base --json --output -o '"$(_bygone_git_refs)" -- "$cur") )
|
|
216
|
+
fi
|
|
217
|
+
;;
|
|
202
218
|
review)
|
|
203
219
|
refs="$(_bygone_git_refs)"
|
|
204
220
|
if [[ "$prev" =~ ^(${baseTokens})$ ]]; then
|
|
@@ -209,7 +225,7 @@ _bygone() {
|
|
|
209
225
|
;;
|
|
210
226
|
present)
|
|
211
227
|
refs="$(_bygone_git_refs)"
|
|
212
|
-
if [[ "$prev" == "--tour" ]]; then
|
|
228
|
+
if [[ "$prev" == "--tour" || "$prev" == "--correspondence" ]]; then
|
|
213
229
|
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
214
230
|
compopt -o filenames 2>/dev/null || true
|
|
215
231
|
elif [[ "$prev" =~ ^(${baseTokens})$ ]]; then
|
|
@@ -261,6 +277,11 @@ function generateFishCompletion() {
|
|
|
261
277
|
}
|
|
262
278
|
|
|
263
279
|
lines.push(
|
|
280
|
+
"complete -c bygone -n 'string match -q \"*tour context*\" -- (commandline -opc)' -l dependencies -d 'Include static module dependency evidence'",
|
|
281
|
+
"complete -c bygone -n '__fish_seen_subcommand_from dependencies' -l base -r -a '(__bygone_git_refs)' -d 'Set the dependency comparison base'",
|
|
282
|
+
"complete -c bygone -n '__fish_seen_subcommand_from dependencies' -l json -d 'Print dependency evidence as JSON'",
|
|
283
|
+
"complete -c bygone -n '__fish_seen_subcommand_from dependencies' -l output -s o -r -d 'Write dependency evidence'",
|
|
284
|
+
"complete -c bygone -n '__fish_seen_subcommand_from dependencies' -a '(__bygone_git_refs)' -d 'Git ref'",
|
|
264
285
|
"complete -c bygone -n '__fish_seen_subcommand_from completion' -a 'zsh bash fish' -d 'Shell'",
|
|
265
286
|
"complete -c bygone -n '__fish_seen_subcommand_from tour' -a 'context coverage validate compile export schema' -d 'Tour action'",
|
|
266
287
|
"complete -c bygone -n 'string match -q \"*tour context*\" -- (commandline -opc)' -l base -r -a '(__bygone_git_refs)' -d 'Set the change-context base ref'",
|
|
@@ -274,6 +295,7 @@ function generateFishCompletion() {
|
|
|
274
295
|
"complete -c bygone -n '__fish_seen_subcommand_from review' -a '(__bygone_git_refs)' -d 'Git ref'",
|
|
275
296
|
`complete -c bygone -n '__fish_seen_subcommand_from present' ${fishTokens(tokensFor('base'))} -r -a '(__bygone_git_refs)' -d '${escapeSingleQuoted(getCliEntry('base').description)}'`,
|
|
276
297
|
`complete -c bygone -n '__fish_seen_subcommand_from present' ${fishTokens(tokensFor('tour'))} -r -d '${escapeSingleQuoted(getCliEntry('tour').description)}'`,
|
|
298
|
+
`complete -c bygone ${fishTokens(tokensFor('correspondenceEvidence'))} -r -d 'Use pinned symbol correspondence evidence'`,
|
|
277
299
|
"complete -c bygone -n '__fish_seen_subcommand_from present' -a '(__bygone_git_refs)' -d 'Git ref'",
|
|
278
300
|
`complete -c bygone -n 'string match -q "*--branch-diff*" -- (commandline -opc)' ${fishTokens(tokensFor('branch'))} -r -a '(__bygone_git_refs)' -d '${escapeSingleQuoted(getCliEntry('branch').description)}'`,
|
|
279
301
|
`complete -c bygone -n 'string match -q "*--branch-diff*" -- (commandline -opc)' ${fishTokens(tokensFor('base'))} -r -a '(__bygone_git_refs)' -d '${escapeSingleQuoted(getCliEntry('base').description)}'`,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { lstatSync, mkdirSync, statSync, writeFileSync } = require('fs');
|
|
3
|
+
const { resolveBranchReviewRange } = require('../out/gitComparison');
|
|
4
|
+
const { importChangeCorrespondence, readCorrespondenceJson } = require('../out/changeCorrespondence');
|
|
5
|
+
|
|
6
|
+
function importCorrespondenceCommand(args, cwd, output = process.stdout) {
|
|
7
|
+
if (args[0] !== 'import') throw new Error('Usage: bygone correspondence import <report.json> [<head>] --base <base> [--case <id>] --output <artifact.json>');
|
|
8
|
+
const options = {};
|
|
9
|
+
const positional = [];
|
|
10
|
+
for (let i = 1; i < args.length; i++) {
|
|
11
|
+
const arg = args[i];
|
|
12
|
+
if (['--base', '--case', '--output', '-o'].includes(arg)) {
|
|
13
|
+
if (!args[i + 1] || args[i + 1].startsWith('-')) throw new Error(`${arg} requires a value.`);
|
|
14
|
+
options[arg === '-o' ? '--output' : arg] = args[++i];
|
|
15
|
+
} else if (arg.startsWith('-')) throw new Error(`Unknown correspondence option: ${arg}`);
|
|
16
|
+
else positional.push(arg);
|
|
17
|
+
}
|
|
18
|
+
if (!positional.length || positional.length > 2 || !options['--output']) throw new Error('Provide a producer report, at most one head ref, and --output.');
|
|
19
|
+
const reportPath = path.resolve(cwd, positional[0]);
|
|
20
|
+
const destination = path.resolve(cwd, options['--output']);
|
|
21
|
+
assertDestinationAvailable(reportPath, destination);
|
|
22
|
+
const report = readCorrespondenceJson(reportPath);
|
|
23
|
+
if (!report || typeof report !== 'object') throw new Error('Invalid correspondence report.');
|
|
24
|
+
let selected = report;
|
|
25
|
+
if (Array.isArray(report.cases)) {
|
|
26
|
+
if (!options['--case']) throw new Error('A multi-case producer report requires --case.');
|
|
27
|
+
const matches = report.cases.filter(c => c.id === options['--case']);
|
|
28
|
+
if (matches.length !== 1) throw new Error('Select exactly one existing producer case.');
|
|
29
|
+
selected = matches[0];
|
|
30
|
+
} else if (options['--case']) throw new Error('--case requires a multi-case producer report.');
|
|
31
|
+
const range = resolveBranchReviewRange(cwd, positional[1] || 'HEAD', options['--base']);
|
|
32
|
+
const artifact = importChangeCorrespondence(range.repoRoot, selected, { baseOid: range.mergeBaseOid, headOid: range.headOid });
|
|
33
|
+
mkdirSync(path.dirname(destination), { recursive: true });
|
|
34
|
+
try {
|
|
35
|
+
writeFileSync(destination, JSON.stringify(artifact, null, 2) + '\n', { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error?.code === 'EEXIST') throw new Error(`The correspondence output already exists at ${destination}; choose a new path.`, { cause: error });
|
|
38
|
+
throw new Error(`Could not create correspondence output at ${destination}: ${error.message}`, { cause: error });
|
|
39
|
+
}
|
|
40
|
+
output.write(`Wrote ${artifact.relations.length} inferred correspondences (${artifact.status}) to ${destination}\n`);
|
|
41
|
+
return artifact;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function assertDestinationAvailable(reportPath, destination) {
|
|
45
|
+
if (sameActualFile(reportPath, destination)) {
|
|
46
|
+
throw new Error('The imported artifact must not overwrite the producer report or any of its symlink/hardlink aliases.');
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
lstatSync(destination);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error?.code === 'ENOENT') return;
|
|
52
|
+
throw new Error(`Could not inspect correspondence output at ${destination}: ${error.message}`, { cause: error });
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`The correspondence output already exists at ${destination}; choose a new path.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sameActualFile(first, second) {
|
|
58
|
+
if (first === second) return true;
|
|
59
|
+
try {
|
|
60
|
+
const left = statSync(first);
|
|
61
|
+
const right = statSync(second);
|
|
62
|
+
if (left.dev !== undefined && left.ino !== undefined
|
|
63
|
+
&& left.dev === right.dev && left.ino === right.ino
|
|
64
|
+
&& left.ino !== 0 && right.ino !== 0) return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { importCorrespondenceCommand };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { mkdirSync, writeFileSync } = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function runDependenciesCommand(args, cwd, output = process.stdout) {
|
|
5
|
+
let headRef, baseRef, outputPath;
|
|
6
|
+
let json = false;
|
|
7
|
+
for (let index = 0; index < args.length; index++) {
|
|
8
|
+
const arg = args[index];
|
|
9
|
+
if (arg === '--json') { json = true; continue; }
|
|
10
|
+
if (['--base', '-m', '--main', '--output', '-o'].includes(arg)) {
|
|
11
|
+
const value = args[++index];
|
|
12
|
+
if (!value || value.startsWith('-')) throw new Error(`${arg} requires a value.`);
|
|
13
|
+
if (['--output', '-o'].includes(arg)) outputPath = value;
|
|
14
|
+
else baseRef = value;
|
|
15
|
+
} else if (arg.startsWith('-') || headRef) throw new Error(`Unexpected dependencies argument: ${arg}`);
|
|
16
|
+
else headRef = arg;
|
|
17
|
+
}
|
|
18
|
+
const { buildDependencyGraph } = require('../out/dependencyGraph.js');
|
|
19
|
+
const report = buildDependencyGraph(cwd, { headRef, baseRef });
|
|
20
|
+
if (outputPath) {
|
|
21
|
+
const resolved = path.resolve(cwd, outputPath);
|
|
22
|
+
mkdirSync(path.dirname(resolved), { recursive: true });
|
|
23
|
+
writeFileSync(resolved, `${JSON.stringify(report, null, 2)}\n`);
|
|
24
|
+
output.write(`Wrote dependency evidence to ${resolved}\n`);
|
|
25
|
+
} else output.write(json ? `${JSON.stringify(report, null, 2)}\n` : renderDependencyReport(report));
|
|
26
|
+
return report;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function renderDependencyReport(report) {
|
|
30
|
+
const complete = report.base.coverage.indexComplete && report.head.coverage.indexComplete;
|
|
31
|
+
const lines = [
|
|
32
|
+
`Module dependencies: ${report.range.baseOid.slice(0, 7)} → ${report.range.headOid.slice(0, 7)}`,
|
|
33
|
+
`Static JS/TS evidence; initialization labels describe syntax, not observed execution.`,
|
|
34
|
+
`Index coverage: ${complete ? 'complete within supported scope' : 'PARTIAL — deltas and consumer lists may be incomplete'}`
|
|
35
|
+
];
|
|
36
|
+
for (const side of ['base', 'head']) {
|
|
37
|
+
const snapshot = report[side];
|
|
38
|
+
const coverage = snapshot.coverage;
|
|
39
|
+
lines.push(`${side}: ${coverage.indexedFiles}/${coverage.candidateFiles} source files, ${snapshot.edges.length} sites, ${coverage.unresolvedEdges} unresolved, ${snapshot.diagnostics.length + coverage.omittedDiagnostics} diagnostics`);
|
|
40
|
+
}
|
|
41
|
+
const site = edge => `${edge.source.file}:${edge.source.line}:${edge.source.column} → ${edge.target ?? edge.specifier ?? '<computed>'} [${edge.relationship}; ${edge.phase}; ${edge.loading}; ${edge.resolution}]`;
|
|
42
|
+
const groups = [
|
|
43
|
+
['Added module edges', report.changes.added], ['Removed module edges', report.changes.removed],
|
|
44
|
+
['Unchanged consumers (base)', report.unchangedConsumers.base], ['Unchanged consumers (head)', report.unchangedConsumers.head]
|
|
45
|
+
];
|
|
46
|
+
for (const [label, edges] of groups) {
|
|
47
|
+
lines.push(`\n${label}: ${edges.length}`);
|
|
48
|
+
for (const edge of edges.slice(0, 50)) {
|
|
49
|
+
lines.push(` ${site(edge)}`);
|
|
50
|
+
if (edge.source.link) lines.push(` ${edge.source.link}`);
|
|
51
|
+
}
|
|
52
|
+
if (edges.length > 50) lines.push(` ${edges.length - 50} more sites; use --json for the bounded full report.`);
|
|
53
|
+
}
|
|
54
|
+
for (const side of ['base', 'head']) {
|
|
55
|
+
lines.push(`\n${side} diagnostics:`);
|
|
56
|
+
for (const item of report[side].diagnostics.slice(0, 10)) lines.push(` ${item.file ? `${item.file}: ` : ''}${item.code}: ${item.message}`);
|
|
57
|
+
if (report[side].diagnostics.length > 10) lines.push(' More diagnostics in --json.');
|
|
58
|
+
}
|
|
59
|
+
lines.push('\nUse --json or --output <file> for all indexed edges, potential initialization calls, exact source ranges, and scope assumptions.');
|
|
60
|
+
return `${lines.join('\n')}\n`;
|
|
61
|
+
}
|
|
62
|
+
module.exports = { runDependenciesCommand, renderDependencyReport };
|
package/cli/evidence.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const workspace = require('../out/reviewWorkspace');
|
|
5
|
+
const { parseReview } = require('../out/requirements');
|
|
6
|
+
const { inspectDependencies } = require('../out/reviewDependencies');
|
|
7
|
+
|
|
8
|
+
function runEvidenceCommand(args, cwd) {
|
|
9
|
+
const [action, input, ...rest] = args;
|
|
10
|
+
if (!['inspect', 'import', 'refresh', 'tour'].includes(action) || !input) throw new Error('Usage: bygone evidence <inspect|import|refresh|tour> <review.json> [--head <revision>] [--requirements <review.json>] [--correspondence <artifact.json>] [--dependencies <report.json>] [--output <new-file>]');
|
|
11
|
+
const options = {};
|
|
12
|
+
for (let i = 0; i < rest.length; i += 2) {
|
|
13
|
+
if (!['--head', '--requirements', '--correspondence', '--dependencies', '--output'].includes(rest[i]) || !rest[i + 1] || options[rest[i]]) throw new Error('Invalid or duplicate evidence option');
|
|
14
|
+
options[rest[i]] = rest[i + 1];
|
|
15
|
+
}
|
|
16
|
+
const root = workspace.repositoryRoot(cwd), file = path.resolve(cwd, input);
|
|
17
|
+
const loaded = workspace.loadReview(root, file);
|
|
18
|
+
// Reports must describe the sanitized artifact that is actually imported.
|
|
19
|
+
const review = action === 'import' ? { ...loaded, decisions: [] } : loaded;
|
|
20
|
+
const absolute = key => options[key] ? path.resolve(cwd, options[key]) : undefined;
|
|
21
|
+
const output = absolute('--output');
|
|
22
|
+
if (action === 'refresh') {
|
|
23
|
+
if (!output || !options['--head']) throw new Error('Refresh requires --head and a new --output file');
|
|
24
|
+
const revised = options['--requirements'] ? parseReview(workspace.readEvidenceJson(absolute('--requirements'))) : review;
|
|
25
|
+
if (revised.repository !== review.repository || revised.base !== review.base || revised.head !== review.head) throw new Error('Requirement update must identify the original repository and comparison');
|
|
26
|
+
const requirements = revised.requirements;
|
|
27
|
+
const result = workspace.refreshWorkspace(root, review, options['--head'], absolute('--correspondence'), requirements);
|
|
28
|
+
workspace.writeReview(output, result.review);
|
|
29
|
+
process.stdout.write(JSON.stringify({ output, transitions: result.transitions }, null, 2) + '\n'); return;
|
|
30
|
+
}
|
|
31
|
+
const report = workspace.inspectWorkspace(root, review);
|
|
32
|
+
const dependencies = options['--dependencies'] ? inspectDependencies(root, review, workspace.readEvidenceJson(absolute('--dependencies'))) : undefined;
|
|
33
|
+
if (action === 'import') {
|
|
34
|
+
if (!output) throw new Error('Import requires a new --output file');
|
|
35
|
+
// Compatible producers export this versioned shape directly. Never manufacture an inspection decision.
|
|
36
|
+
workspace.writeReview(output, review);
|
|
37
|
+
process.stdout.write(JSON.stringify({ output, report, dependencies }, null, 2) + '\n'); return;
|
|
38
|
+
}
|
|
39
|
+
if (action === 'tour') {
|
|
40
|
+
if (!output) throw new Error('Tour requires a new --output file');
|
|
41
|
+
const anchors = {}, steps = [];
|
|
42
|
+
for (const requirement of report.requirements) for (const link of requirement.links) {
|
|
43
|
+
if (link.resolution.status !== 'valid') continue;
|
|
44
|
+
const text = workspace.sourceText(root, link.ref), needle = link.resolution.text;
|
|
45
|
+
let at = -1, occurrence = 0;
|
|
46
|
+
do { at = text.indexOf(needle, at + 1); occurrence++; } while (at >= 0 && at < link.ref.start);
|
|
47
|
+
if (at !== link.ref.start) throw new Error('Cannot represent source reference as a tour anchor');
|
|
48
|
+
const id = `evidence-${steps.length + 1}`;
|
|
49
|
+
anchors[id] = { file: link.ref.path, revision: link.ref.revision === review.base ? 'base' : 'head', contains: needle, occurrence };
|
|
50
|
+
steps.push({ id, title: `${requirement.id}: ${link.role}`, body: `${link.rationale}\nVerification: ${requirement.verification}; inspection: ${requirement.reviewer}. Reference validity does not establish fulfillment.`, focus: id,
|
|
51
|
+
requirement: { id: requirement.id, text: requirement.text, source: requirement.sources.map(s => s.title).join('; ') } });
|
|
52
|
+
}
|
|
53
|
+
if (!steps.length) throw new Error('No valid evidence links to present; use evidence inspect for missing/broken evidence');
|
|
54
|
+
const tour = { version: 6, title: 'Requirement evidence', range: { base: review.base, head: review.head }, anchors, connections: [],
|
|
55
|
+
chapters: [{ id: 'requirements', title: 'Requirement evidence', scenes: [{ id: 'evidence', kind: 'walkthrough', title: 'Inspect recorded evidence',
|
|
56
|
+
summary: 'Pinned references, execution records, and human inspection remain separate.', tags: [],
|
|
57
|
+
bullets: report.requirements.filter(r => !r.implementationLinked || !r.testLinked).map(r => `${r.id}: missing implementation or test links`),
|
|
58
|
+
takeaway: 'This tour supplies inspection context, not an approval decision.', steps }] }] };
|
|
59
|
+
// Validate before publishing an artifact. The existing tour engine owns presentation.
|
|
60
|
+
require('../out/changeTour').buildChangeTourManifest(root, { source: tour });
|
|
61
|
+
fs.writeFileSync(output, JSON.stringify(tour, null, 2) + '\n', { flag: 'wx' });
|
|
62
|
+
process.stdout.write(`Created ${output}\nOpen: bygone -C ${JSON.stringify(root)} present --tour ${JSON.stringify(output)}\n`); return;
|
|
63
|
+
}
|
|
64
|
+
const result = JSON.stringify({ review, report, dependencies }, null, 2) + '\n';
|
|
65
|
+
if (output) fs.writeFileSync(output, result, { flag: 'wx' }); else process.stdout.write(result);
|
|
66
|
+
}
|
|
67
|
+
module.exports = { runEvidenceCommand };
|