@davmash/bygone 0.11.0 → 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 +6 -0
- package/README.md +57 -25
- package/bin/bygone.js +11 -1
- package/cli/commandSpec.js +12 -1
- package/cli/completions.js +23 -2
- package/cli/dependencies.js +62 -0
- package/cli/evidence.js +67 -0
- package/cli/tour.js +11 -1
- package/completions/_bygone +11 -1
- package/completions/bygone +10 -2
- package/completions/bygone.fish +7 -0
- package/docs/dependencies.md +94 -0
- package/docs/evidence-review.md +163 -0
- package/docs/product-surface.md +2 -1
- package/docs/vscode-integration.md +183 -0
- package/media/webview.js +167 -167
- package/out/changeTour.js +1 -1
- package/out/dependencyGraph.js +213939 -0
- package/out/requirements.js +427 -0
- package/out/reviewDependencies.js +669 -0
- package/out/reviewWorkspace.js +1129 -0
- package/out/standalone-main.js +119 -9
- package/package.json +1 -1
- package/web/export-runtime.json +3 -3
- package/web/index.html +1 -0
- package/web/web-host.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
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
|
+
|
|
3
9
|
## 0.11.0
|
|
4
10
|
|
|
5
11
|
- Added experimental JS/TS and Python symbol evidence with declaration diffs, symbol trees, analysis diagnostics, and shared Files/Symbols navigation
|
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
|
|
@@ -376,31 +378,61 @@ read-only. Desktop hand-offs are unavailable in remote extension hosts.
|
|
|
376
378
|
|
|
377
379
|
## Follow the active VS Code editor
|
|
378
380
|
|
|
379
|
-
Run **Bygone: Follow Active Editor** to open one
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
The
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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.
|
|
404
436
|
|
|
405
437
|
## Limitations
|
|
406
438
|
|
package/bin/bygone.js
CHANGED
|
@@ -40,7 +40,17 @@ if (tokenMatches('completion', args[0])) {
|
|
|
40
40
|
process.exit(0);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
if (args[0] === '
|
|
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') {
|
|
44
54
|
try {
|
|
45
55
|
require('../cli/correspondence').importCorrespondenceCommand(args.slice(1), cliCwd);
|
|
46
56
|
} catch (error) {
|
package/cli/commandSpec.js
CHANGED
|
@@ -16,7 +16,9 @@ const CLI_SPEC = Object.freeze({
|
|
|
16
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>] [--correspondence <artifact.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>]',
|
|
20
22
|
'bygone correspondence import <report.json> [<head>] [--base <base>] [--case <id>] --output <artifact.json>',
|
|
21
23
|
'bygone tour coverage <file.bygone> [--json] [--minimum-coverage <0-100>]',
|
|
22
24
|
'bygone tour export <file.bygone> --output <tour.html> [--profile minimal|full] [--runtime embedded|cdn]',
|
|
@@ -27,6 +29,7 @@ const CLI_SPEC = Object.freeze({
|
|
|
27
29
|
'bygone --test'
|
|
28
30
|
],
|
|
29
31
|
entries: [
|
|
32
|
+
{ id: 'evidence', kind: 'command', tokens: ['evidence'], description: 'Inspect, import, refresh, or present pinned review evidence', argument: 'path' },
|
|
30
33
|
{
|
|
31
34
|
id: 'review',
|
|
32
35
|
kind: 'command',
|
|
@@ -34,6 +37,14 @@ const CLI_SPEC = Object.freeze({
|
|
|
34
37
|
description: 'Review a committed branch against its merge base',
|
|
35
38
|
argument: 'git-ref'
|
|
36
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
|
+
},
|
|
37
48
|
{
|
|
38
49
|
id: 'correspondence', kind: 'command', tokens: ['correspondence'],
|
|
39
50
|
description: 'Import externally produced symbol correspondence evidence', argument: 'path'
|
package/cli/completions.js
CHANGED
|
@@ -96,13 +96,21 @@ ${historyOptions}
|
|
|
96
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 --correspondence
|
|
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
|
|
@@ -192,13 +200,21 @@ _bygone() {
|
|
|
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 --correspondence '"$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
|
|
@@ -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'",
|
|
@@ -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 };
|
package/cli/tour.js
CHANGED
|
@@ -34,13 +34,21 @@ function runTourCommand(args, cwd, packageRoot, output = process.stdout) {
|
|
|
34
34
|
return { action: 'schema' };
|
|
35
35
|
}
|
|
36
36
|
if (options.action === 'context') {
|
|
37
|
+
const range = options.dependencies
|
|
38
|
+
? require('../out/gitComparison.js').resolveBranchReviewRange(cwd, options.headRef, options.baseRef)
|
|
39
|
+
: undefined;
|
|
37
40
|
const context = buildChangeTourContext(cwd, {
|
|
41
|
+
range,
|
|
38
42
|
headRef: options.headRef,
|
|
39
43
|
baseRef: options.baseRef,
|
|
40
44
|
correspondencePath: options.correspondencePath ? path.resolve(cwd, options.correspondencePath) : undefined,
|
|
41
45
|
maxPatchBytes: options.maxPatchBytes,
|
|
42
46
|
maxTotalPatchBytes: options.maxTotalPatchBytes
|
|
43
47
|
});
|
|
48
|
+
if (options.dependencies) {
|
|
49
|
+
const { buildDependencyGraph } = require('../out/dependencyGraph.js');
|
|
50
|
+
context.dependencies = buildDependencyGraph(cwd, { range });
|
|
51
|
+
}
|
|
44
52
|
return writeJsonResult('change-tour context', context, options.outputPath, cwd, output);
|
|
45
53
|
}
|
|
46
54
|
|
|
@@ -150,6 +158,7 @@ function parseContextArgs(args) {
|
|
|
150
158
|
let maxPatchBytes;
|
|
151
159
|
let correspondencePath;
|
|
152
160
|
let maxTotalPatchBytes;
|
|
161
|
+
let dependencies = false;
|
|
153
162
|
for (let index = 0; index < args.length; index += 1) {
|
|
154
163
|
const arg = args[index];
|
|
155
164
|
if (arg === '--base' || arg === '-m' || arg === '--main') {
|
|
@@ -162,6 +171,7 @@ function parseContextArgs(args) {
|
|
|
162
171
|
outputPath = args[++index];
|
|
163
172
|
continue;
|
|
164
173
|
}
|
|
174
|
+
if (arg === '--dependencies') { dependencies = true; continue; }
|
|
165
175
|
if (arg === '--correspondence') {
|
|
166
176
|
if (!args[index + 1] || args[index + 1].startsWith('-')) throw new Error('--correspondence requires a file path.');
|
|
167
177
|
correspondencePath = args[++index];
|
|
@@ -187,7 +197,7 @@ function parseContextArgs(args) {
|
|
|
187
197
|
if (headRef) throw new Error('tour context accepts at most one head ref.');
|
|
188
198
|
headRef = arg;
|
|
189
199
|
}
|
|
190
|
-
return { action: 'context', headRef, baseRef, outputPath, maxPatchBytes, maxTotalPatchBytes, ...(correspondencePath ? { correspondencePath } : {}) };
|
|
200
|
+
return { action: 'context', headRef, baseRef, outputPath, maxPatchBytes, maxTotalPatchBytes, ...(correspondencePath ? { correspondencePath } : {}), ...(dependencies ? { dependencies } : {}) };
|
|
191
201
|
}
|
|
192
202
|
|
|
193
203
|
function writeJsonResult(label, value, outputPath, cwd, output) {
|
package/completions/_bygone
CHANGED
|
@@ -13,7 +13,9 @@ _bygone() {
|
|
|
13
13
|
local previous="${words[CURRENT-1]}"
|
|
14
14
|
local -a root_items review_options present_options legacy_options history_options shells tour_actions
|
|
15
15
|
root_items=(
|
|
16
|
+
'evidence:Inspect, import, refresh, or present pinned review evidence'
|
|
16
17
|
'review:Review a committed branch against its merge base'
|
|
18
|
+
'dependencies:Compare static JS/TS module dependencies at committed revisions'
|
|
17
19
|
'correspondence:Import externally produced symbol correspondence evidence'
|
|
18
20
|
'completion:Print shell completion source'
|
|
19
21
|
'present:Open an app-hosted tour of a committed branch range'
|
|
@@ -83,13 +85,21 @@ _bygone() {
|
|
|
83
85
|
elif [[ "$previous" == "--output" || "$previous" == "-o" || "$previous" == "--correspondence" ]]; then
|
|
84
86
|
_files
|
|
85
87
|
else
|
|
86
|
-
compadd -- --base --output -o --max-patch-bytes --max-total-patch-bytes --correspondence
|
|
88
|
+
compadd -- --base --output -o --max-patch-bytes --max-total-patch-bytes --correspondence --dependencies
|
|
87
89
|
_bygone_git_refs
|
|
88
90
|
fi
|
|
89
91
|
elif [[ "$words[3]" == "coverage" || "$words[3]" == "validate" || "$words[3]" == "compile" ]]; then
|
|
90
92
|
_files -g '*.bygone' -g '*.bygone.yaml'
|
|
91
93
|
fi
|
|
92
94
|
;;
|
|
95
|
+
dependencies)
|
|
96
|
+
if [[ "$previous" == "--output" || "$previous" == "-o" ]]; then
|
|
97
|
+
_files
|
|
98
|
+
else
|
|
99
|
+
compadd -- --base --json --output -o
|
|
100
|
+
_bygone_git_refs
|
|
101
|
+
fi
|
|
102
|
+
;;
|
|
93
103
|
review)
|
|
94
104
|
if [[ "$previous" == (--base|-m|--main) ]]; then
|
|
95
105
|
_bygone_git_refs
|
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 correspondence completion present tour --diff --history --git-diff --branch-diff --test --read-only -C -h --help -v --version' -- "$cur") $(compgen -f -- "$cur") )
|
|
15
|
+
COMPREPLY=( $(compgen -W 'evidence review dependencies correspondence 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
|
|
@@ -32,13 +32,21 @@ _bygone() {
|
|
|
32
32
|
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
33
33
|
compopt -o filenames 2>/dev/null || true
|
|
34
34
|
else
|
|
35
|
-
COMPREPLY=( $(compgen -W '--base --output -o --max-patch-bytes --max-total-patch-bytes --correspondence '"$refs" -- "$cur") )
|
|
35
|
+
COMPREPLY=( $(compgen -W '--base --output -o --max-patch-bytes --max-total-patch-bytes --correspondence --dependencies '"$refs" -- "$cur") )
|
|
36
36
|
fi
|
|
37
37
|
else
|
|
38
38
|
COMPREPLY=( $(compgen -W '--json --output -o' -- "$cur") $(compgen -f -- "$cur") )
|
|
39
39
|
compopt -o filenames 2>/dev/null || true
|
|
40
40
|
fi
|
|
41
41
|
;;
|
|
42
|
+
dependencies)
|
|
43
|
+
if [[ "$prev" == "--output" || "$prev" == "-o" ]]; then
|
|
44
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
45
|
+
compopt -o filenames 2>/dev/null || true
|
|
46
|
+
else
|
|
47
|
+
COMPREPLY=( $(compgen -W '--base --json --output -o '"$(_bygone_git_refs)" -- "$cur") )
|
|
48
|
+
fi
|
|
49
|
+
;;
|
|
42
50
|
review)
|
|
43
51
|
refs="$(_bygone_git_refs)"
|
|
44
52
|
if [[ "$prev" =~ ^(--base|-m|--main)$ ]]; then
|
package/completions/bygone.fish
CHANGED
|
@@ -4,7 +4,9 @@ function __bygone_git_refs
|
|
|
4
4
|
printf "%s\n" HEAD INDEX WORKTREE
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
+
complete -c bygone -n '__fish_use_subcommand' -a evidence -r -d 'Inspect, import, refresh, or present pinned review evidence'
|
|
7
8
|
complete -c bygone -n '__fish_use_subcommand' -a review -d 'Review a committed branch against its merge base'
|
|
9
|
+
complete -c bygone -n '__fish_use_subcommand' -a dependencies -d 'Compare static JS/TS module dependencies at committed revisions'
|
|
8
10
|
complete -c bygone -n '__fish_use_subcommand' -a correspondence -r -d 'Import externally produced symbol correspondence evidence'
|
|
9
11
|
complete -c bygone -n '__fish_use_subcommand' -a completion -d 'Print shell completion source'
|
|
10
12
|
complete -c bygone -n '__fish_use_subcommand' -a present -d 'Open an app-hosted tour of a committed branch range'
|
|
@@ -18,6 +20,11 @@ complete -c bygone -n '__fish_use_subcommand' -l read-only -d 'Open live Explore
|
|
|
18
20
|
complete -c bygone -n '__fish_use_subcommand' -s C -r -d 'Run as if Bygone started in this directory'
|
|
19
21
|
complete -c bygone -n '__fish_use_subcommand' -s h -l help -d 'Show command help'
|
|
20
22
|
complete -c bygone -n '__fish_use_subcommand' -s v -l version -d 'Show the installed version'
|
|
23
|
+
complete -c bygone -n 'string match -q "*tour context*" -- (commandline -opc)' -l dependencies -d 'Include static module dependency evidence'
|
|
24
|
+
complete -c bygone -n '__fish_seen_subcommand_from dependencies' -l base -r -a '(__bygone_git_refs)' -d 'Set the dependency comparison base'
|
|
25
|
+
complete -c bygone -n '__fish_seen_subcommand_from dependencies' -l json -d 'Print dependency evidence as JSON'
|
|
26
|
+
complete -c bygone -n '__fish_seen_subcommand_from dependencies' -l output -s o -r -d 'Write dependency evidence'
|
|
27
|
+
complete -c bygone -n '__fish_seen_subcommand_from dependencies' -a '(__bygone_git_refs)' -d 'Git ref'
|
|
21
28
|
complete -c bygone -n '__fish_seen_subcommand_from completion' -a 'zsh bash fish' -d 'Shell'
|
|
22
29
|
complete -c bygone -n '__fish_seen_subcommand_from tour' -a 'context coverage validate compile export schema' -d 'Tour action'
|
|
23
30
|
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'
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Dependency evidence
|
|
2
|
+
|
|
3
|
+
The Node CLI (npm installation or `node bin/bygone.js` in a source checkout)
|
|
4
|
+
compares JS/TS module dependencies at the merge base and head of a committed
|
|
5
|
+
review range:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bygone dependencies HEAD --base main
|
|
9
|
+
bygone dependencies HEAD --base main --json
|
|
10
|
+
bygone dependencies HEAD --base main --output dependencies.json
|
|
11
|
+
bygone tour context HEAD --base main --dependencies --output context.json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The text report lists added/removed module edges and direct, unchanged consumers
|
|
15
|
+
of changed files. Each site includes a `bygone://` link to its committed source.
|
|
16
|
+
JSON includes both bounded snapshot graphs, potential initialization calls and
|
|
17
|
+
constructors, diagnostics, and exact evidence coordinates. Ordinary tour context
|
|
18
|
+
generation is unchanged. The desktop and VS Code interfaces do not yet have a
|
|
19
|
+
graph view; these commands use the Node CLI rather than the desktop launcher.
|
|
20
|
+
|
|
21
|
+
## Reading an edge
|
|
22
|
+
|
|
23
|
+
Relationship, phase, and loading are separate fields. Static evidence can describe
|
|
24
|
+
initialization or later invocation; it is not a runtime trace.
|
|
25
|
+
|
|
26
|
+
- `import`, `reexport`, `require`, and `dynamic-import` describe syntax.
|
|
27
|
+
- `type` marks explicitly type-only dependencies and dependencies in declaration files. `module-init` marks syntax
|
|
28
|
+
evaluated while a module initializes; a lazy-loaded module can initialize much
|
|
29
|
+
later than process startup. `invocation` marks function/callback bodies.
|
|
30
|
+
`unknown` retains sites where the extractor cannot assign a phase safely.
|
|
31
|
+
- `eager` distinguishes ordinary imports/require from `deferred` dynamic imports and namespace `import defer` declarations. Deferred namespace evaluation has `unknown` phase because namespace use determines when evaluation occurs.
|
|
32
|
+
A dynamic import at top level is still labeled `module-init`. A `require` inside
|
|
33
|
+
a function is `invocation` even though the require operation itself is eager.
|
|
34
|
+
- `resolution: resolved` means the TypeScript resolver found a committed target
|
|
35
|
+
under the selected compiler options. It does not prove execution, runtime loader
|
|
36
|
+
equivalence, or the identity of a shadowable `require` binding.
|
|
37
|
+
- `unresolved`, `computed`, and `invalid-config` preserve unknown targets. Builtins,
|
|
38
|
+
external packages, and computed specifiers commonly remain unresolved.
|
|
39
|
+
|
|
40
|
+
Potential initialization calls are syntax sites, not inferred function-call
|
|
41
|
+
edges. Registration calls can appear here; their callback bodies are classified
|
|
42
|
+
separately. IIFE bodies are conservatively labeled invocation with a diagnostic.
|
|
43
|
+
Instance field initializers have unknown timing; static fields and class static
|
|
44
|
+
blocks inherit the enclosing evaluation phase. Conditions are not evaluated.
|
|
45
|
+
Non-explicit type import elision and transpiler-specific emission are not modeled.
|
|
46
|
+
|
|
47
|
+
## Historical resolution and coverage
|
|
48
|
+
|
|
49
|
+
Both graphs are built from Git tree/blob objects at immutable OIDs. Dirty files,
|
|
50
|
+
installed packages, symlink targets, compiler plugins and target code are never
|
|
51
|
+
read or executed for resolution. The nearest committed `tsconfig.json` (or
|
|
52
|
+
`jsconfig.json`) supplies compiler options, including relative `extends`, `baseUrl`
|
|
53
|
+
and `paths`. Missing/invalid config is diagnosed. Without a config, analysis uses
|
|
54
|
+
TypeScript's Node-style resolution with JavaScript enabled. The report records
|
|
55
|
+
the analyzer's TypeScript version; it need not match the target project's compiler.
|
|
56
|
+
The dependency analyzer uses an isolated TypeScript 5.9.3 parser/resolver (without changing Strand or the extension compiler), including `bundler` resolution and modern syntax such as `using`. Package-based config extension and bundler runtime plugins remain outside scope. Malformed package metadata produces partial coverage; resolution that needs that metadata is `invalid-config`, without a fallback target.
|
|
57
|
+
Project references do not provide symbol binding or build output reconstruction.
|
|
58
|
+
|
|
59
|
+
Limits apply per snapshot: 2,000 source/config files, 1 MiB per file, 16 MiB total
|
|
60
|
+
input and 20,000 dependency/initialization records. Source-ancestor package.json, tsconfig.json and jsconfig.json files, plus their relative config inheritance closure, share the input budget and are read first. Directory imports can load other required package metadata on demand through the same bounds. Unrelated JSON, package files and lockfiles do not consume this budget. Relative inheritance supports transitive and array parents within the same bounds. Only UTF-8 text is parsed.
|
|
61
|
+
The dependency analysis budget is 30 seconds, including standalone Git range
|
|
62
|
+
discovery and both snapshots. It is checked between parsing/resolution operations;
|
|
63
|
+
it is not a hard interrupt inside the parser. Tour context reuses its pinned range,
|
|
64
|
+
and its ordinary context generation is outside this budget. Git tree enumeration is
|
|
65
|
+
bounded to 16 MiB. Up to 1,000 diagnostics per snapshot are retained with an omitted
|
|
66
|
+
count. Programmatic callers may lower these limits.
|
|
67
|
+
|
|
68
|
+
`coverage` reports supported source candidates, indexed/omitted files, unresolved
|
|
69
|
+
edges and whether indexing completed within the supported scope. Configuration,
|
|
70
|
+
parser and scope diagnostics matter even when an endpoint was found. A resolved
|
|
71
|
+
target can itself be outside the indexed subset. Partial graphs produce provisional
|
|
72
|
+
deltas and consumer lists; missing edges never establish independence. Submodules,
|
|
73
|
+
non-JS/TS languages and installed `node_modules` are outside scope.
|
|
74
|
+
|
|
75
|
+
The delta compares module endpoints, relationship, phase and loading, aligning
|
|
76
|
+
Git-detected renames. Source spelling/location and repeated occurrences alone do
|
|
77
|
+
not constitute new module edges. When a module edge changes, all matching source
|
|
78
|
+
sites appear in the delta. Alias/config retargeting can change dependencies of an
|
|
79
|
+
unchanged file. Consumer lists are one hop and include type-only edges, with phases
|
|
80
|
+
retained so callers can filter them. They are not a transitive impact analysis.
|
|
81
|
+
|
|
82
|
+
Every source reference contains a commit OID, Git blob OID, repository-relative
|
|
83
|
+
path, UTF-16 half-open range, one-based line/column, and SHA-256 of the selected
|
|
84
|
+
UTF-8 text. Links are local to the repository that generated the report. Equal
|
|
85
|
+
base/head revisions and paths outside the deep-link grammar retain coordinates
|
|
86
|
+
without a link. No source content is embedded in the dependency report itself.
|
|
87
|
+
|
|
88
|
+
Runtime trace ingestion, resolved symbol/call graphs and visual graph layout are
|
|
89
|
+
later slices. No workload is run to generate this evidence.
|
|
90
|
+
|
|
91
|
+
The [validation corpus and measured results](../scripts/dependency-benchmark/README.md)
|
|
92
|
+
cover labeled module edges, source evidence, bounded scale and pinned public
|
|
93
|
+
repositories. The historical baseline documents compatibility and metadata gaps; the follow-up validation records their fixes. Complete indexing is not proof that runtime
|
|
94
|
+
loading is valid.
|