agentweaver 0.1.19 → 0.1.20
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/README.md +47 -7
- package/dist/artifacts.js +9 -0
- package/dist/executors/git-commit-executor.js +24 -6
- package/dist/flow-state.js +3 -8
- package/dist/git/git-diff-parser.js +223 -0
- package/dist/git/git-service.js +562 -0
- package/dist/git/git-stage-selection.js +24 -0
- package/dist/git/git-status-parser.js +171 -0
- package/dist/git/git-types.js +1 -0
- package/dist/index.js +450 -108
- package/dist/interactive/auto-flow.js +644 -0
- package/dist/interactive/controller.js +417 -9
- package/dist/interactive/progress.js +194 -1
- package/dist/interactive/state.js +25 -0
- package/dist/interactive/web/index.js +97 -12
- package/dist/interactive/web/protocol.js +216 -1
- package/dist/interactive/web/server.js +72 -14
- package/dist/interactive/web/static/app.js +1603 -49
- package/dist/interactive/web/static/index.html +76 -11
- package/dist/interactive/web/static/styles.css +1 -1
- package/dist/interactive/web/static/styles.input.css +901 -47
- package/dist/pipeline/auto-flow-blocks.js +307 -0
- package/dist/pipeline/auto-flow-config.js +273 -0
- package/dist/pipeline/auto-flow-identity.js +49 -0
- package/dist/pipeline/auto-flow-presets.js +52 -0
- package/dist/pipeline/auto-flow-resolver.js +830 -0
- package/dist/pipeline/auto-flow-types.js +17 -0
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/declarative-flows.js +27 -1
- package/dist/pipeline/flow-specs/auto-common-guided.json +11 -0
- package/dist/pipeline/flow-specs/auto-golang.json +12 -1
- package/dist/pipeline/flow-specs/bugz/bug-analyze.json +54 -1
- package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +19 -1
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +33 -1
- package/dist/pipeline/flow-specs/review/review-project.json +19 -1
- package/dist/pipeline/flow-specs/task-source/manual-jira-input.json +70 -0
- package/dist/pipeline/node-registry.js +9 -0
- package/dist/pipeline/nodes/codex-prompt-node.js +8 -1
- package/dist/pipeline/nodes/flow-run-node.js +5 -3
- package/dist/pipeline/nodes/git-status-node.js +2 -168
- package/dist/pipeline/nodes/manual-jira-task-input-node.js +146 -0
- package/dist/pipeline/nodes/opencode-prompt-node.js +8 -1
- package/dist/pipeline/nodes/plan-codex-node.js +8 -1
- package/dist/pipeline/spec-loader.js +14 -4
- package/dist/runtime/artifact-catalog.js +29 -5
- package/dist/runtime/settings.js +114 -0
- package/dist/scope.js +14 -4
- package/package.json +1 -1
- package/dist/pipeline/flow-specs/auto-common.json +0 -179
- package/dist/pipeline/flow-specs/auto-simple.json +0 -141
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ In practice, this means you can treat an agent workflow like an engineered syste
|
|
|
56
56
|
|
|
57
57
|
- `resume` only resumes a genuinely interrupted run and uses the saved execution state without rebuilding already completed steps
|
|
58
58
|
- `continue` is intended for completed iterative cycles and starts the next iteration from the latest valid artifacts without deleting historical artifacts
|
|
59
|
-
- `restart` is treated as a new run
|
|
59
|
+
- `restart` is treated as a new run. For end-to-end attempt flows, the current active attempt is archived under `.agentweaver/scopes/<scope>/.artifacts/restart-archives/attempt-XXXX` before the new attempt starts; for independent single-purpose flows, restart only resets that flow's saved state and keeps existing scope artifacts available.
|
|
60
60
|
- For ambiguous launches, the operator must choose the action explicitly: by confirmation in interactive mode, or with `--resume`, `--continue`, or `--restart` in non-interactive mode
|
|
61
61
|
- This contract applies to `auto-common`, `auto-simple`, `auto-golang`, `instant-task`, `review-loop`, `run-go-linter-loop`, and `run-go-tests-loop`
|
|
62
62
|
|
|
@@ -92,7 +92,7 @@ The full flow-spec reference now lives in [docs/declarative-workflows.md](docs/d
|
|
|
92
92
|
|
|
93
93
|
User-invokable built-in commands currently map to these flow specs:
|
|
94
94
|
|
|
95
|
-
- `plan` —
|
|
95
|
+
- `plan` — uses a normalized task source from Jira or manual input, generates clarifying questions for the developer, collects answers, and produces design, implementation plan, and QA plan as structured JSON and markdown artifacts
|
|
96
96
|
- `design-review` — performs a structured critique of the latest planning artifacts and writes a dedicated `design-review/v1` artifact; `approved_with_warnings` is treated as ready to proceed and may still produce `ready-to-merge.md`
|
|
97
97
|
- `task-describe` — generates a brief task description from a Jira issue or from manual input; when Jira is provided, fetches the issue and summarizes it; otherwise accepts free-form text and analyzes the codebase to produce a richer description
|
|
98
98
|
- `implement` — runs LLM-backed implementation based on previously approved design and plan artifacts; executes code changes locally in the project working directory
|
|
@@ -144,11 +144,11 @@ By default, AgentWeaver tries to open the browser after the server starts succes
|
|
|
144
144
|
|
|
145
145
|
The Web UI serves the operator console from the same local process, including `/`, `/static/app.js`, and `/static/styles.css`. Live browser interaction uses WebSocket on `/__agentweaver/ws`. Bounded checks can use `GET /__agentweaver/health`, and shutdown is available through `POST /__agentweaver/exit` or `SIGINT`/`SIGTERM`.
|
|
146
146
|
|
|
147
|
-
Web UI state is process-local:
|
|
147
|
+
Web UI session state is process-local: active flow selection, confirmations, forms, progress, and logs exist only while the AgentWeaver process is running and are not shared with other AgentWeaver processes. Visual preferences such as theme, panel sizes, and log auto-scroll are stored in the global AgentWeaver settings file at `~/.agentweaver/settings.json`, so they survive host and port changes.
|
|
148
148
|
|
|
149
149
|
### Artifact Explorer
|
|
150
150
|
|
|
151
|
-
After a Web UI workflow completes, and also after a failed run when artifacts were written before failure, the Web UI offers the Artifact Explorer. It reads artifacts from the active AgentWeaver scope
|
|
151
|
+
After a Web UI workflow completes, and also after a failed run when artifacts were written before failure, the Web UI offers the Artifact Explorer. It reads markdown artifacts from the active AgentWeaver scope, including artifacts from earlier runs in that scope; the latest workflow run is used only to choose the initial preview when possible. The browser requests artifact content through safe artifact identifiers resolved by the active catalog and registry; it does not accept arbitrary filesystem paths.
|
|
152
152
|
|
|
153
153
|
The MVP explorer previews Markdown, JSON, plain text, and diff artifacts. Diffs are rendered as text. Binary and unknown artifacts are listed with metadata and safe raw/download actions, but their inline preview is a placeholder. Large previews are bounded and marked as truncated with loaded byte and total size metadata; raw and download links can still serve the full artifact bytes with no-store cache headers and safe content types.
|
|
154
154
|
|
|
@@ -300,6 +300,10 @@ agentweaver gitlab-review
|
|
|
300
300
|
agentweaver mr-description DEMO-1234
|
|
301
301
|
agentweaver run-go-tests-loop DEMO-1234
|
|
302
302
|
agentweaver run-go-linter-loop DEMO-1234
|
|
303
|
+
agentweaver auto DEMO-1234
|
|
304
|
+
agentweaver auto --preset simple DEMO-1234
|
|
305
|
+
agentweaver auto --preset standard --dry-run-flow DEMO-1234
|
|
306
|
+
agentweaver auto --config backend-standard --dry-run-flow DEMO-1234
|
|
303
307
|
agentweaver auto-golang DEMO-1234
|
|
304
308
|
agentweaver auto-common DEMO-1234
|
|
305
309
|
agentweaver auto-simple DEMO-1234
|
|
@@ -315,6 +319,7 @@ node dist/index.js plan DEMO-1234
|
|
|
315
319
|
node dist/index.js design-review DEMO-1234
|
|
316
320
|
node dist/index.js implement DEMO-1234
|
|
317
321
|
node dist/index.js review DEMO-1234
|
|
322
|
+
node dist/index.js auto --preset standard --dry-run-flow DEMO-1234
|
|
318
323
|
node dist/index.js auto-golang DEMO-1234
|
|
319
324
|
node dist/index.js auto-common DEMO-1234
|
|
320
325
|
```
|
|
@@ -337,12 +342,17 @@ agentweaver doctor --json
|
|
|
337
342
|
Notes:
|
|
338
343
|
|
|
339
344
|
- `--dry` fetches required context but prints launch commands instead of running Codex/OpenCode steps
|
|
345
|
+
- `--dry-run-flow` applies only to `agentweaver auto`; it validates and previews the resolved flow without running workflow steps or writing resolver artifacts
|
|
346
|
+
- `--preset <simple|standard>` applies only to `agentweaver auto`; raw `agentweaver auto` defaults to `--preset standard`
|
|
347
|
+
- `--config <name>` applies only to `agentweaver auto`; it loads a saved YAML config by name
|
|
340
348
|
- `--verbose` streams child process stdout/stderr in direct CLI mode
|
|
341
349
|
- `--prompt <text>` appends extra instructions to the prompt
|
|
342
350
|
- `--scope <name>` is supported by scope-flexible flows such as `implement`, `review`, `review-fix`, `review-loop`, `run-go-tests-loop`, `run-go-linter-loop`, `gitlab-review`, and `gitlab-diff-review`
|
|
343
351
|
- `--md-lang <en|ru>` applies only to generated workflow markdown artifacts, not repository source files or committed documentation
|
|
344
352
|
- `--force` only affects interactive mode: it skips loading cached summary-pane content on startup so Jira-backed flows that regenerate summary artifacts can repopulate it during the run
|
|
345
|
-
-
|
|
353
|
+
- Saved auto flow configs are discovered at `.agentweaver/flow-configs/<name>.yaml` first and `~/.agentweaver/flow-configs/<name>.yaml` second; the project config wins when both exist
|
|
354
|
+
- Non-dry `agentweaver auto` runs write `flow-config.yaml`, `resolved-flow.json`, and `resolved-flow-summary.json` under `.agentweaver/scopes/<scopeKey>/.artifacts`
|
|
355
|
+
- `auto-golang`, `auto-common-guided`, `auto-common`, `auto-simple`, and configurable `auto` ask for Jira input interactively when Jira input is omitted; leave it empty to paste task text in the next step when Jira is unavailable
|
|
346
356
|
- `task-describe` can also work from manual task description input without Jira
|
|
347
357
|
- `gitlab-review` and `gitlab-diff-review` ask for a GitLab merge request URL interactively
|
|
348
358
|
- `auto-status` and `auto-reset` currently operate on persisted state for `auto-golang`
|
|
@@ -361,6 +371,36 @@ Notes:
|
|
|
361
371
|
|
|
362
372
|
`auto-simple` is the preserved simplified pipeline: `plan → implement → review loop`, with no planning review gate and no revise rounds. It is behaviorally equivalent to the legacy `auto-common` before the planning gate was introduced.
|
|
363
373
|
|
|
374
|
+
`agentweaver auto` is the configurable entrypoint in front of those built-in flows. Without flags it resolves to the standard preset, equivalent to `auto-common`. `--preset simple` resolves to the simplified flow, and `--preset standard` resolves to the design-review-gated flow. Use `--dry-run-flow` to inspect the selected source, phase order, included/skipped blocks, max-iteration settings, and artifact policy without invoking LLM executors, command executors, nested flows, state mutation, or resolver artifact writes.
|
|
375
|
+
|
|
376
|
+
Saved configs are YAML files named by command flag:
|
|
377
|
+
|
|
378
|
+
```yaml
|
|
379
|
+
kind: auto-flow-config
|
|
380
|
+
version: 1
|
|
381
|
+
name: backend-standard
|
|
382
|
+
basePreset: standard
|
|
383
|
+
slots:
|
|
384
|
+
designReview:
|
|
385
|
+
blocks:
|
|
386
|
+
- id: review.design-loop
|
|
387
|
+
enabled: true
|
|
388
|
+
maxIterations: 3
|
|
389
|
+
postImplementationChecks:
|
|
390
|
+
blocks:
|
|
391
|
+
- id: checks.go.linter
|
|
392
|
+
enabled: true
|
|
393
|
+
maxIterations: 5
|
|
394
|
+
review:
|
|
395
|
+
blocks:
|
|
396
|
+
- id: review.loop
|
|
397
|
+
enabled: true
|
|
398
|
+
final:
|
|
399
|
+
blocks: []
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Supported presets are `simple` and `standard`. Supported slots are `designReview`, `postImplementationChecks`, `review`, and `final`. Supported block ids are `review.design-loop`, `checks.go.linter`, `checks.go.tests`, and `review.loop`. `enabled` accepts `true`, `false`, or `auto`; `maxIterations`, when present, must be a positive integer.
|
|
403
|
+
|
|
364
404
|
## Launch Profiles and Resume
|
|
365
405
|
|
|
366
406
|
Interactive flow runs can ask for an LLM launch profile: executor plus model. That selection is persisted with resumable flow state.
|
|
@@ -505,10 +545,10 @@ Use the guided flow with:
|
|
|
505
545
|
|
|
506
546
|
```bash
|
|
507
547
|
agentweaver auto-common-guided --help-phases
|
|
508
|
-
agentweaver auto-common-guided --accept-playbook-draft DEMO-1234
|
|
548
|
+
agentweaver auto-common-guided --accept-playbook-draft [DEMO-1234]
|
|
509
549
|
```
|
|
510
550
|
|
|
511
|
-
The workflow does not read old `playbook.json` or `playbook.md` files as fallbacks. In non-interactive runs, a missing manifest stops the workflow before planning and reports the required action: run `agentweaver playbook-init --accept-playbook-draft` first, or rerun `agentweaver auto-common-guided --accept-playbook-draft <jira
|
|
551
|
+
The workflow does not read old `playbook.json` or `playbook.md` files as fallbacks. In non-interactive runs, a missing manifest stops the workflow before planning and reports the required action: run `agentweaver playbook-init --accept-playbook-draft` first, or rerun `agentweaver auto-common-guided --accept-playbook-draft [<jira>]` in an interactive terminal when manual task input is needed. The `--accept-playbook-draft` flag explicitly accepts the generated playbook without interactive review and allows AgentWeaver to write the manifest-based layout. An invalid manifest stops the guided phase before the LLM prompt.
|
|
512
552
|
|
|
513
553
|
To inspect whether playbook guidance participated in a run, check the generated artifacts:
|
|
514
554
|
|
package/dist/artifacts.js
CHANGED
|
@@ -30,6 +30,15 @@ export function artifactManifestSidecarPath(payloadPath) {
|
|
|
30
30
|
export function artifactIndexFile(scopeKey) {
|
|
31
31
|
return scopeArtifactsFile(scopeKey, "artifact-index.json");
|
|
32
32
|
}
|
|
33
|
+
export function flowConfigYamlFile(scopeKey) {
|
|
34
|
+
return scopeArtifactsFile(scopeKey, "flow-config.yaml");
|
|
35
|
+
}
|
|
36
|
+
export function resolvedFlowJsonFile(scopeKey) {
|
|
37
|
+
return scopeArtifactsFile(scopeKey, "resolved-flow.json");
|
|
38
|
+
}
|
|
39
|
+
export function resolvedFlowSummaryJsonFile(scopeKey) {
|
|
40
|
+
return scopeArtifactsFile(scopeKey, "resolved-flow-summary.json");
|
|
41
|
+
}
|
|
33
42
|
export function taskWorkspaceDir(taskKey) {
|
|
34
43
|
return scopeWorkspaceDir(taskKey);
|
|
35
44
|
}
|
|
@@ -1,18 +1,36 @@
|
|
|
1
|
+
import { selectPathsNeedingGitStage, uniqueGitPaths } from "../git/git-stage-selection.js";
|
|
2
|
+
import { parsePorcelain } from "../git/git-status-parser.js";
|
|
1
3
|
export const gitCommitExecutor = {
|
|
2
4
|
kind: "git-commit",
|
|
3
5
|
version: 1,
|
|
4
6
|
defaultConfig: {},
|
|
5
7
|
async execute(context, input) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const files = uniqueGitPaths(input.files);
|
|
9
|
+
if (files.length > 0) {
|
|
10
|
+
let filesToStage = files;
|
|
11
|
+
if (!context.dryRun) {
|
|
12
|
+
const statusOutput = await context.runtime.runCommand(["git", "status", "--porcelain", "--", ...files], {
|
|
13
|
+
dryRun: false,
|
|
14
|
+
verbose: context.verbose,
|
|
15
|
+
label: "git status",
|
|
16
|
+
printFailureOutput: false,
|
|
17
|
+
});
|
|
18
|
+
filesToStage = selectPathsNeedingGitStage(files, parsePorcelain(statusOutput));
|
|
19
|
+
}
|
|
20
|
+
if (filesToStage.length > 0) {
|
|
21
|
+
await context.runtime.runCommand(["git", "add", "-A", "--", ...filesToStage], {
|
|
22
|
+
dryRun: context.dryRun,
|
|
23
|
+
verbose: context.verbose,
|
|
24
|
+
label: "git add",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
12
27
|
}
|
|
13
28
|
const commitArgs = input.editEnabled
|
|
14
29
|
? ["git", "commit", "-e", "-m", input.message]
|
|
15
30
|
: ["git", "commit", "-m", input.message];
|
|
31
|
+
if (files.length > 0) {
|
|
32
|
+
commitArgs.push("--", ...files);
|
|
33
|
+
}
|
|
16
34
|
const output = await context.runtime.runCommand(commitArgs, {
|
|
17
35
|
dryRun: context.dryRun,
|
|
18
36
|
verbose: context.verbose,
|
package/dist/flow-state.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { ensureScopeWorkspaceDir, flowStateFile } from "./artifacts.js";
|
|
4
4
|
import { TaskRunnerError } from "./errors.js";
|
|
5
|
+
import { isContinuableParentFlowId } from "./pipeline/auto-flow-identity.js";
|
|
5
6
|
import { isFlowRunResumeEnvelope } from "./pipeline/flow-run-resume.js";
|
|
6
7
|
import { resolveStoredExecutionRoutingSnapshot, singleLaunchProfileExecutionRouting } from "./runtime/execution-routing.js";
|
|
7
8
|
const FLOW_STATE_SCHEMA_VERSION = 3;
|
|
@@ -12,12 +13,6 @@ const CONTINUABLE_FLOW_KINDS = new Set([
|
|
|
12
13
|
"run-go-linter-loop-flow",
|
|
13
14
|
"run-go-tests-loop-flow",
|
|
14
15
|
]);
|
|
15
|
-
const CONTINUABLE_PARENT_FLOW_IDS = new Set([
|
|
16
|
-
"auto-common",
|
|
17
|
-
"auto-simple",
|
|
18
|
-
"auto-golang",
|
|
19
|
-
"instant-task",
|
|
20
|
-
]);
|
|
21
16
|
const CONTINUABLE_DIRECT_FLOW_IDS = new Set([
|
|
22
17
|
"review-loop",
|
|
23
18
|
"run-go-linter-loop",
|
|
@@ -118,7 +113,7 @@ function parseTerminationLocation(terminationReason) {
|
|
|
118
113
|
function inferContinuationMetadata(flowId, executionState) {
|
|
119
114
|
const stopLocation = parseTerminationLocation(executionState.terminationReason);
|
|
120
115
|
const continueEligible = CONTINUABLE_FLOW_KINDS.has(executionState.flowKind)
|
|
121
|
-
|| (
|
|
116
|
+
|| (isContinuableParentFlowId(flowId) && Boolean(stopLocation.stopPhaseId && stopLocation.stopStepId));
|
|
122
117
|
return {
|
|
123
118
|
continueEligible,
|
|
124
119
|
...(stopLocation.stopPhaseId ? { stopPhaseId: stopLocation.stopPhaseId } : {}),
|
|
@@ -262,7 +257,7 @@ export function classifyFlowLaunchAvailability(state) {
|
|
|
262
257
|
},
|
|
263
258
|
restart: {
|
|
264
259
|
available: true,
|
|
265
|
-
reason: "
|
|
260
|
+
reason: "Start a fresh run.",
|
|
266
261
|
},
|
|
267
262
|
};
|
|
268
263
|
return availability;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
function splitLines(value) {
|
|
2
|
+
if (!value) {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
5
|
+
return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
6
|
+
}
|
|
7
|
+
function parseHunkHeader(line) {
|
|
8
|
+
const match = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/);
|
|
9
|
+
if (!match) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
header: line,
|
|
14
|
+
oldStart: Number(match[1]),
|
|
15
|
+
oldLines: Number(match[2] ?? "1"),
|
|
16
|
+
newStart: Number(match[3]),
|
|
17
|
+
newLines: Number(match[4] ?? "1"),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function normalizeGitPath(value) {
|
|
21
|
+
return value.replace(/^"?[ab]\//, "").replace(/"$/, "");
|
|
22
|
+
}
|
|
23
|
+
function parseHeaderMetadata(lines) {
|
|
24
|
+
const metadata = {};
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
if (line.startsWith("rename from ")) {
|
|
27
|
+
metadata.originalPath = normalizeGitPath(line.slice("rename from ".length));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return metadata;
|
|
31
|
+
}
|
|
32
|
+
function isBinaryDiff(lines) {
|
|
33
|
+
return lines.some((line) => (/^Binary files .+ and .+ differ$/.test(line)
|
|
34
|
+
|| line === "GIT binary patch"
|
|
35
|
+
|| line.startsWith("literal ")
|
|
36
|
+
|| line.startsWith("delta ")));
|
|
37
|
+
}
|
|
38
|
+
function pushPairedRows(rows, deletes, adds) {
|
|
39
|
+
const pairedCount = Math.min(deletes.length, adds.length);
|
|
40
|
+
for (let index = 0; index < pairedCount; index += 1) {
|
|
41
|
+
const left = deletes[index];
|
|
42
|
+
const right = adds[index];
|
|
43
|
+
rows.push({
|
|
44
|
+
kind: "modify",
|
|
45
|
+
leftLineNumber: left.leftLineNumber,
|
|
46
|
+
rightLineNumber: right.rightLineNumber,
|
|
47
|
+
leftText: left.text,
|
|
48
|
+
rightText: right.text,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
for (let index = pairedCount; index < deletes.length; index += 1) {
|
|
52
|
+
const row = deletes[index];
|
|
53
|
+
rows.push({
|
|
54
|
+
kind: "delete",
|
|
55
|
+
leftLineNumber: row.leftLineNumber,
|
|
56
|
+
rightLineNumber: null,
|
|
57
|
+
leftText: row.text,
|
|
58
|
+
rightText: "",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
for (let index = pairedCount; index < adds.length; index += 1) {
|
|
62
|
+
const row = adds[index];
|
|
63
|
+
rows.push({
|
|
64
|
+
kind: "add",
|
|
65
|
+
leftLineNumber: null,
|
|
66
|
+
rightLineNumber: row.rightLineNumber,
|
|
67
|
+
leftText: "",
|
|
68
|
+
rightText: row.text,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function normalizeRows(rawRows) {
|
|
73
|
+
const rows = [];
|
|
74
|
+
let index = 0;
|
|
75
|
+
while (index < rawRows.length) {
|
|
76
|
+
const current = rawRows[index];
|
|
77
|
+
if (current.kind !== "delete") {
|
|
78
|
+
rows.push(current.kind === "context"
|
|
79
|
+
? {
|
|
80
|
+
kind: "context",
|
|
81
|
+
leftLineNumber: current.leftLineNumber,
|
|
82
|
+
rightLineNumber: current.rightLineNumber,
|
|
83
|
+
leftText: current.text,
|
|
84
|
+
rightText: current.text,
|
|
85
|
+
}
|
|
86
|
+
: {
|
|
87
|
+
kind: "add",
|
|
88
|
+
leftLineNumber: null,
|
|
89
|
+
rightLineNumber: current.rightLineNumber,
|
|
90
|
+
leftText: "",
|
|
91
|
+
rightText: current.text,
|
|
92
|
+
});
|
|
93
|
+
index += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const deletes = [];
|
|
97
|
+
while (rawRows[index]?.kind === "delete") {
|
|
98
|
+
deletes.push(rawRows[index]);
|
|
99
|
+
index += 1;
|
|
100
|
+
}
|
|
101
|
+
const adds = [];
|
|
102
|
+
while (rawRows[index]?.kind === "add") {
|
|
103
|
+
adds.push(rawRows[index]);
|
|
104
|
+
index += 1;
|
|
105
|
+
}
|
|
106
|
+
if (adds.length > 0) {
|
|
107
|
+
pushPairedRows(rows, deletes, adds);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
pushPairedRows(rows, deletes, []);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return rows;
|
|
114
|
+
}
|
|
115
|
+
export function parseGitDiffOutput(output, options) {
|
|
116
|
+
const lines = splitLines(output);
|
|
117
|
+
const metadata = parseHeaderMetadata(lines);
|
|
118
|
+
if (isBinaryDiff(lines)) {
|
|
119
|
+
return {
|
|
120
|
+
mode: options.mode,
|
|
121
|
+
path: options.path,
|
|
122
|
+
displayPath: options.displayPath ?? options.path,
|
|
123
|
+
...(options.originalPath ?? metadata.originalPath ? { originalPath: options.originalPath ?? metadata.originalPath } : {}),
|
|
124
|
+
binary: true,
|
|
125
|
+
tooLarge: false,
|
|
126
|
+
empty: false,
|
|
127
|
+
hunks: [],
|
|
128
|
+
message: "Binary file diff is not displayed.",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const hunks = [];
|
|
132
|
+
let current = null;
|
|
133
|
+
let leftLine = 0;
|
|
134
|
+
let rightLine = 0;
|
|
135
|
+
function finishCurrent() {
|
|
136
|
+
if (!current) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
hunks.push({
|
|
140
|
+
header: current.header,
|
|
141
|
+
oldStart: current.oldStart,
|
|
142
|
+
oldLines: current.oldLines,
|
|
143
|
+
newStart: current.newStart,
|
|
144
|
+
newLines: current.newLines,
|
|
145
|
+
rows: normalizeRows(current.rawRows),
|
|
146
|
+
});
|
|
147
|
+
current = null;
|
|
148
|
+
}
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
const header = parseHunkHeader(line);
|
|
151
|
+
if (header) {
|
|
152
|
+
finishCurrent();
|
|
153
|
+
current = { ...header, rawRows: [] };
|
|
154
|
+
leftLine = header.oldStart;
|
|
155
|
+
rightLine = header.newStart;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!current) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (line.startsWith("\")) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const marker = line[0] ?? "";
|
|
165
|
+
const value = line.length > 0 ? line.slice(1) : "";
|
|
166
|
+
if (marker === " ") {
|
|
167
|
+
current.rawRows.push({ kind: "context", leftLineNumber: leftLine, rightLineNumber: rightLine, text: value });
|
|
168
|
+
leftLine += 1;
|
|
169
|
+
rightLine += 1;
|
|
170
|
+
}
|
|
171
|
+
else if (marker === "-") {
|
|
172
|
+
current.rawRows.push({ kind: "delete", leftLineNumber: leftLine, rightLineNumber: null, text: value });
|
|
173
|
+
leftLine += 1;
|
|
174
|
+
}
|
|
175
|
+
else if (marker === "+") {
|
|
176
|
+
current.rawRows.push({ kind: "add", leftLineNumber: null, rightLineNumber: rightLine, text: value });
|
|
177
|
+
rightLine += 1;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
finishCurrent();
|
|
181
|
+
return {
|
|
182
|
+
mode: options.mode,
|
|
183
|
+
path: options.path,
|
|
184
|
+
displayPath: options.displayPath ?? options.path,
|
|
185
|
+
...(options.originalPath ?? metadata.originalPath ? { originalPath: options.originalPath ?? metadata.originalPath } : {}),
|
|
186
|
+
binary: false,
|
|
187
|
+
tooLarge: false,
|
|
188
|
+
empty: hunks.length === 0 || hunks.every((hunk) => hunk.rows.length === 0),
|
|
189
|
+
hunks,
|
|
190
|
+
...(hunks.length === 0 ? { message: "No diff is available for this mode." } : {}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export function createSyntheticAddedDiff(input) {
|
|
194
|
+
const lines = splitLines(input.content);
|
|
195
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
196
|
+
lines.pop();
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
mode: input.mode,
|
|
200
|
+
path: input.path,
|
|
201
|
+
displayPath: input.displayPath ?? input.path,
|
|
202
|
+
binary: false,
|
|
203
|
+
tooLarge: false,
|
|
204
|
+
empty: lines.length === 0,
|
|
205
|
+
hunks: lines.length === 0
|
|
206
|
+
? []
|
|
207
|
+
: [{
|
|
208
|
+
header: `@@ -0,0 +1,${lines.length} @@`,
|
|
209
|
+
oldStart: 0,
|
|
210
|
+
oldLines: 0,
|
|
211
|
+
newStart: 1,
|
|
212
|
+
newLines: lines.length,
|
|
213
|
+
rows: lines.map((line, index) => ({
|
|
214
|
+
kind: "add",
|
|
215
|
+
leftLineNumber: null,
|
|
216
|
+
rightLineNumber: index + 1,
|
|
217
|
+
leftText: "",
|
|
218
|
+
rightText: line,
|
|
219
|
+
})),
|
|
220
|
+
}],
|
|
221
|
+
...(lines.length === 0 ? { message: "Untracked file is empty." } : {}),
|
|
222
|
+
};
|
|
223
|
+
}
|