@rasensio/aidlc 0.1.0
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 +251 -0
- package/capabilities/claude-code.yaml +9 -0
- package/capabilities/codex.yaml +8 -0
- package/capabilities/cursor.yaml +8 -0
- package/capabilities/kiro.yaml +9 -0
- package/capabilities/windsurf.yaml +8 -0
- package/ci/aidlc-gate.yml +100 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add-action.d.ts +83 -0
- package/dist/commands/add-action.d.ts.map +1 -0
- package/dist/commands/add-action.js +437 -0
- package/dist/commands/add-action.js.map +1 -0
- package/dist/commands/continue.d.ts +23 -0
- package/dist/commands/continue.d.ts.map +1 -0
- package/dist/commands/continue.js +238 -0
- package/dist/commands/continue.js.map +1 -0
- package/dist/commands/discover.d.ts +19 -0
- package/dist/commands/discover.d.ts.map +1 -0
- package/dist/commands/discover.js +85 -0
- package/dist/commands/discover.js.map +1 -0
- package/dist/commands/docs.d.ts +25 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +282 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/gate.d.ts +25 -0
- package/dist/commands/gate.d.ts.map +1 -0
- package/dist/commands/gate.js +168 -0
- package/dist/commands/gate.js.map +1 -0
- package/dist/commands/init.d.ts +24 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +187 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/review.d.ts +60 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +264 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/setup.d.ts +24 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +206 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/start.d.ts +27 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +243 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +25 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +263 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/transition.d.ts +30 -0
- package/dist/commands/transition.d.ts.map +1 -0
- package/dist/commands/transition.js +353 -0
- package/dist/commands/transition.js.map +1 -0
- package/dist/compile/adapters/claude-code.d.ts +24 -0
- package/dist/compile/adapters/claude-code.d.ts.map +1 -0
- package/dist/compile/adapters/claude-code.js +271 -0
- package/dist/compile/adapters/claude-code.js.map +1 -0
- package/dist/compile/adapters/codex.d.ts +19 -0
- package/dist/compile/adapters/codex.d.ts.map +1 -0
- package/dist/compile/adapters/codex.js +157 -0
- package/dist/compile/adapters/codex.js.map +1 -0
- package/dist/compile/adapters/cursor.d.ts +21 -0
- package/dist/compile/adapters/cursor.d.ts.map +1 -0
- package/dist/compile/adapters/cursor.js +181 -0
- package/dist/compile/adapters/cursor.js.map +1 -0
- package/dist/compile/adapters/index.d.ts +16 -0
- package/dist/compile/adapters/index.d.ts.map +1 -0
- package/dist/compile/adapters/index.js +26 -0
- package/dist/compile/adapters/index.js.map +1 -0
- package/dist/compile/adapters/kiro.d.ts +24 -0
- package/dist/compile/adapters/kiro.d.ts.map +1 -0
- package/dist/compile/adapters/kiro.js +222 -0
- package/dist/compile/adapters/kiro.js.map +1 -0
- package/dist/compile/adapters/windsurf.d.ts +25 -0
- package/dist/compile/adapters/windsurf.d.ts.map +1 -0
- package/dist/compile/adapters/windsurf.js +242 -0
- package/dist/compile/adapters/windsurf.js.map +1 -0
- package/dist/compile/install-plan.d.ts +49 -0
- package/dist/compile/install-plan.d.ts.map +1 -0
- package/dist/compile/install-plan.js +112 -0
- package/dist/compile/install-plan.js.map +1 -0
- package/dist/compile/loaders.d.ts +81 -0
- package/dist/compile/loaders.d.ts.map +1 -0
- package/dist/compile/loaders.js +293 -0
- package/dist/compile/loaders.js.map +1 -0
- package/dist/compile/merger.d.ts +32 -0
- package/dist/compile/merger.d.ts.map +1 -0
- package/dist/compile/merger.js +60 -0
- package/dist/compile/merger.js.map +1 -0
- package/dist/core/gate.d.ts +36 -0
- package/dist/core/gate.d.ts.map +1 -0
- package/dist/core/gate.js +97 -0
- package/dist/core/gate.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lifecycle.d.ts +89 -0
- package/dist/core/lifecycle.d.ts.map +1 -0
- package/dist/core/lifecycle.js +356 -0
- package/dist/core/lifecycle.js.map +1 -0
- package/dist/core/template-resolver.d.ts +56 -0
- package/dist/core/template-resolver.d.ts.map +1 -0
- package/dist/core/template-resolver.js +261 -0
- package/dist/core/template-resolver.js.map +1 -0
- package/dist/core/types.d.ts +527 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +8 -0
- package/dist/core/types.js.map +1 -0
- package/dist/discover/scanner.d.ts +58 -0
- package/dist/discover/scanner.d.ts.map +1 -0
- package/dist/discover/scanner.js +772 -0
- package/dist/discover/scanner.js.map +1 -0
- package/dist/events/event-bus.d.ts +53 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +263 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/events/plugin-hooks.d.ts +44 -0
- package/dist/events/plugin-hooks.d.ts.map +1 -0
- package/dist/events/plugin-hooks.js +225 -0
- package/dist/events/plugin-hooks.js.map +1 -0
- package/dist/orchestrator/budget-packer.d.ts +57 -0
- package/dist/orchestrator/budget-packer.d.ts.map +1 -0
- package/dist/orchestrator/budget-packer.js +70 -0
- package/dist/orchestrator/budget-packer.js.map +1 -0
- package/dist/orchestrator/config.d.ts +43 -0
- package/dist/orchestrator/config.d.ts.map +1 -0
- package/dist/orchestrator/config.js +109 -0
- package/dist/orchestrator/config.js.map +1 -0
- package/dist/orchestrator/endpoint-clients.d.ts +23 -0
- package/dist/orchestrator/endpoint-clients.d.ts.map +1 -0
- package/dist/orchestrator/endpoint-clients.js +129 -0
- package/dist/orchestrator/endpoint-clients.js.map +1 -0
- package/dist/orchestrator/orchestrator-loop.d.ts +93 -0
- package/dist/orchestrator/orchestrator-loop.d.ts.map +1 -0
- package/dist/orchestrator/orchestrator-loop.js +185 -0
- package/dist/orchestrator/orchestrator-loop.js.map +1 -0
- package/dist/security/confirmation.d.ts +90 -0
- package/dist/security/confirmation.d.ts.map +1 -0
- package/dist/security/confirmation.js +140 -0
- package/dist/security/confirmation.js.map +1 -0
- package/dist/setup/setup-compute.d.ts +54 -0
- package/dist/setup/setup-compute.d.ts.map +1 -0
- package/dist/setup/setup-compute.js +66 -0
- package/dist/setup/setup-compute.js.map +1 -0
- package/dist/state/snapshot-store.d.ts +79 -0
- package/dist/state/snapshot-store.d.ts.map +1 -0
- package/dist/state/snapshot-store.js +164 -0
- package/dist/state/snapshot-store.js.map +1 -0
- package/dist/state/state-recovery.d.ts +87 -0
- package/dist/state/state-recovery.d.ts.map +1 -0
- package/dist/state/state-recovery.js +274 -0
- package/dist/state/state-recovery.js.map +1 -0
- package/dist/state/transition-log.d.ts +40 -0
- package/dist/state/transition-log.d.ts.map +1 -0
- package/dist/state/transition-log.js +83 -0
- package/dist/state/transition-log.js.map +1 -0
- package/dist/state/yaml-helpers.d.ts +53 -0
- package/dist/state/yaml-helpers.d.ts.map +1 -0
- package/dist/state/yaml-helpers.js +110 -0
- package/dist/state/yaml-helpers.js.map +1 -0
- package/examples/.gitkeep +0 -0
- package/guidance/accessibility.md +67 -0
- package/guidance/api-conventions.md +124 -0
- package/guidance/index.yaml +7 -0
- package/guidance/secure-defaults.md +71 -0
- package/package.json +52 -0
- package/phases/.gitkeep +0 -0
- package/skills/00-overview.md +74 -0
- package/skills/10-ideation.md +45 -0
- package/skills/20-requirements.md +54 -0
- package/skills/30-design.md +57 -0
- package/skills/40-implementation.md +64 -0
- package/skills/50-testing.md +60 -0
- package/skills/60-deployment.md +63 -0
- package/skills/70-maintenance.md +56 -0
- package/skills/80-review.md +85 -0
- package/skills/81-continue.md +57 -0
- package/skills/82-add-action.md +71 -0
- package/templates/.gitkeep +0 -0
- package/templates/bugfix.yaml +14 -0
- package/templates/full-feature.yaml +44 -0
- package/templates/micro-task.yaml +11 -0
- package/templates/quick-feature.yaml +20 -0
- package/templates/spike.yaml +15 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot store with atomic writes and per-instance namespaces.
|
|
3
|
+
*
|
|
4
|
+
* All instance state lives under `.aidlc/state/<instance-name>/`:
|
|
5
|
+
* - `instance.yaml` — instance metadata
|
|
6
|
+
* - `phase-<name>.yaml` — per-phase state (one per applicable phase)
|
|
7
|
+
*
|
|
8
|
+
* Write semantics:
|
|
9
|
+
* - Snapshot files use write-to-tmp + rename for atomicity (Req 23.5).
|
|
10
|
+
* Readers never observe partial writes.
|
|
11
|
+
* - Instance directories are fully isolated — edits to one instance
|
|
12
|
+
* never touch another instance's files (Req 23.1, 23.3, 6.1).
|
|
13
|
+
*
|
|
14
|
+
* Requirements: 4.1, 6.1, 23.1, 23.3, 23.5
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync, writeFileSync, renameSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
17
|
+
import { join, dirname } from 'node:path';
|
|
18
|
+
import { parse, stringify } from 'yaml';
|
|
19
|
+
import { YamlLoadError } from './yaml-helpers.js';
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// YAML serialization options (deterministic, matching yaml-helpers.ts)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
const SERIALIZE_OPTIONS = {
|
|
24
|
+
sortMapEntries: true,
|
|
25
|
+
blockQuote: 'literal',
|
|
26
|
+
lineWidth: 0,
|
|
27
|
+
};
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Atomic write primitive
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/**
|
|
32
|
+
* Write content to a file atomically using tmp-file + rename.
|
|
33
|
+
*
|
|
34
|
+
* 1. Write to `<filePath>.tmp.<pid>` — unique per process, avoids collisions.
|
|
35
|
+
* 2. Rename the tmp file to the target path — atomic on POSIX filesystems.
|
|
36
|
+
*
|
|
37
|
+
* Parent directories are created if they don't exist.
|
|
38
|
+
*/
|
|
39
|
+
export function atomicWrite(filePath, content) {
|
|
40
|
+
const tmpPath = `${filePath}.tmp.${process.pid}`;
|
|
41
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
42
|
+
writeFileSync(tmpPath, content, 'utf8');
|
|
43
|
+
renameSync(tmpPath, filePath);
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Path helpers
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
/**
|
|
49
|
+
* Compute the absolute directory path for an instance namespace.
|
|
50
|
+
*
|
|
51
|
+
* @param stateDir - Root state directory (e.g., `/project/.aidlc/state`).
|
|
52
|
+
* @param instance - Instance name (URL-safe directory name).
|
|
53
|
+
* @returns Absolute path: `<stateDir>/<instance>/`.
|
|
54
|
+
*/
|
|
55
|
+
export function instancePath(stateDir, instance) {
|
|
56
|
+
return join(stateDir, instance);
|
|
57
|
+
}
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Directory operations
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
/**
|
|
62
|
+
* Create the namespace directory for a new lifecycle instance.
|
|
63
|
+
*
|
|
64
|
+
* @param stateDir - Root state directory.
|
|
65
|
+
* @param name - Instance name.
|
|
66
|
+
* @returns The absolute path to the created directory.
|
|
67
|
+
*/
|
|
68
|
+
export function createInstanceDir(stateDir, name) {
|
|
69
|
+
const dir = instancePath(stateDir, name);
|
|
70
|
+
mkdirSync(dir, { recursive: true });
|
|
71
|
+
return dir;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* List all instance namespace directories under the state root.
|
|
75
|
+
*
|
|
76
|
+
* Returns an array of instance names (directory basenames).
|
|
77
|
+
* Only directories are included — stray files at the state root are ignored.
|
|
78
|
+
*
|
|
79
|
+
* @param stateDir - Root state directory.
|
|
80
|
+
* @returns Array of instance names (directory names).
|
|
81
|
+
*/
|
|
82
|
+
export function listInstanceDirs(stateDir) {
|
|
83
|
+
let entries;
|
|
84
|
+
try {
|
|
85
|
+
entries = readdirSync(stateDir);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const code = err.code;
|
|
89
|
+
if (code === 'ENOENT') {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
return entries.filter((entry) => {
|
|
95
|
+
try {
|
|
96
|
+
return statSync(join(stateDir, entry)).isDirectory();
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Save / Load snapshots
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
/**
|
|
107
|
+
* Save a snapshot file atomically within an instance namespace.
|
|
108
|
+
*
|
|
109
|
+
* Serializes `data` to deterministic YAML and writes it using
|
|
110
|
+
* tmp-file + rename to guarantee atomic writes (Req 23.5).
|
|
111
|
+
*
|
|
112
|
+
* @param stateDir - Root state directory.
|
|
113
|
+
* @param instance - Instance name.
|
|
114
|
+
* @param fileName - File name within the instance directory (e.g., `instance.yaml`).
|
|
115
|
+
* @param data - Data to serialize and persist.
|
|
116
|
+
*/
|
|
117
|
+
export function saveSnapshot(stateDir, instance, fileName, data) {
|
|
118
|
+
const filePath = join(instancePath(stateDir, instance), fileName);
|
|
119
|
+
const content = stringify(data, SERIALIZE_OPTIONS);
|
|
120
|
+
atomicWrite(filePath, content);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Load and parse a snapshot file from an instance namespace.
|
|
124
|
+
*
|
|
125
|
+
* Optionally validates the parsed data using a schema validator.
|
|
126
|
+
* Throws `YamlLoadError` on missing files, parse errors, or validation failures
|
|
127
|
+
* — always naming the file in the error message (Req 4.7).
|
|
128
|
+
*
|
|
129
|
+
* @param stateDir - Root state directory.
|
|
130
|
+
* @param instance - Instance name.
|
|
131
|
+
* @param fileName - File name within the instance directory.
|
|
132
|
+
* @param validate - Optional schema validator.
|
|
133
|
+
* @returns The parsed (and validated) data cast to `T`.
|
|
134
|
+
* @throws {YamlLoadError} If the file is missing, corrupt, or fails validation.
|
|
135
|
+
*/
|
|
136
|
+
export function loadSnapshot(stateDir, instance, fileName, validate) {
|
|
137
|
+
const filePath = join(instancePath(stateDir, instance), fileName);
|
|
138
|
+
let raw;
|
|
139
|
+
try {
|
|
140
|
+
raw = readFileSync(filePath, 'utf8');
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const code = err.code;
|
|
144
|
+
if (code === 'ENOENT') {
|
|
145
|
+
throw new YamlLoadError(filePath, 'file not found');
|
|
146
|
+
}
|
|
147
|
+
throw new YamlLoadError(filePath, `unable to read file: ${err.message}`);
|
|
148
|
+
}
|
|
149
|
+
let data;
|
|
150
|
+
try {
|
|
151
|
+
data = parse(raw);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
throw new YamlLoadError(filePath, `YAML parse error: ${err.message}`);
|
|
155
|
+
}
|
|
156
|
+
if (validate) {
|
|
157
|
+
const error = validate(data);
|
|
158
|
+
if (error) {
|
|
159
|
+
throw new YamlLoadError(filePath, `validation failed: ${error}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return data;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=snapshot-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-store.js","sourceRoot":"","sources":["../../src/state/snapshot-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corrupt/missing state handling with recovery support.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - `loadWithRecovery()` — load a state file, reporting errors clearly
|
|
6
|
+
* with optional backup before any recovery attempt.
|
|
7
|
+
* - `backupStateFile()` — create a timestamped backup copy of a file.
|
|
8
|
+
* - `detectConflictMarkers()` — scan a file for git conflict markers and
|
|
9
|
+
* produce a report with resolution steps.
|
|
10
|
+
*
|
|
11
|
+
* Key invariant: the original file is NEVER modified or discarded.
|
|
12
|
+
* Backups are copies; the original stays intact until the user explicitly
|
|
13
|
+
* resolves the issue.
|
|
14
|
+
*
|
|
15
|
+
* Requirements: 4.6, 4.7
|
|
16
|
+
*/
|
|
17
|
+
import type { SchemaValidator } from './yaml-helpers.js';
|
|
18
|
+
/**
|
|
19
|
+
* Result of a recovery-aware load attempt.
|
|
20
|
+
*
|
|
21
|
+
* - `data`: the parsed content if successful, null otherwise.
|
|
22
|
+
* - `recovered`: true if a backup was created during this load attempt.
|
|
23
|
+
* - `backupPath`: path to the backup file if one was created, null otherwise.
|
|
24
|
+
* - `error`: human-readable error message if loading failed, null on success.
|
|
25
|
+
*/
|
|
26
|
+
export interface RecoveryResult<T> {
|
|
27
|
+
data: T | null;
|
|
28
|
+
recovered: boolean;
|
|
29
|
+
backupPath: string | null;
|
|
30
|
+
error: string | null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Report produced when git conflict markers are detected in a state file.
|
|
34
|
+
*/
|
|
35
|
+
export interface ConflictReport {
|
|
36
|
+
/** Path to the file containing conflict markers. */
|
|
37
|
+
filePath: string;
|
|
38
|
+
/** Fields that appear to be in conflict (best-effort extraction). */
|
|
39
|
+
conflictingFields: string[];
|
|
40
|
+
/** Human-readable resolution steps. */
|
|
41
|
+
resolutionSteps: string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create a timestamped backup of a state file.
|
|
45
|
+
*
|
|
46
|
+
* Naming convention: `<filename>.backup.<ISO-timestamp>`
|
|
47
|
+
* (colons in the timestamp are replaced with hyphens for filesystem safety).
|
|
48
|
+
*
|
|
49
|
+
* The original file is NOT modified — the backup is a pure copy.
|
|
50
|
+
*
|
|
51
|
+
* @param filePath - Absolute path to the file to back up.
|
|
52
|
+
* @returns The absolute path to the created backup file.
|
|
53
|
+
* @throws {YamlLoadError} If the source file does not exist.
|
|
54
|
+
*/
|
|
55
|
+
export declare function backupStateFile(filePath: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Detect git conflict markers in a file and produce a resolution report.
|
|
58
|
+
*
|
|
59
|
+
* Scans the file content for `<<<<<<<`, `=======`, `>>>>>>>` marker lines.
|
|
60
|
+
* If any are found, attempts to identify which YAML fields are in conflict
|
|
61
|
+
* by parsing the "ours" and "theirs" sections.
|
|
62
|
+
*
|
|
63
|
+
* @param filePath - Absolute path to the file to check.
|
|
64
|
+
* @returns A ConflictReport if markers are found, null otherwise.
|
|
65
|
+
*/
|
|
66
|
+
export declare function detectConflictMarkers(filePath: string): ConflictReport | null;
|
|
67
|
+
/**
|
|
68
|
+
* Load a state file with recovery support.
|
|
69
|
+
*
|
|
70
|
+
* Behavior:
|
|
71
|
+
* - If the file is missing: returns an error result naming the file.
|
|
72
|
+
* - If the file has conflict markers: returns an error result describing
|
|
73
|
+
* the conflicts, optionally backs up before returning.
|
|
74
|
+
* - If the file is corrupt (invalid YAML or fails validation):
|
|
75
|
+
* a) Reports which file is invalid and why.
|
|
76
|
+
* b) Optionally creates a backup copy before returning.
|
|
77
|
+
* c) NEVER modifies or discards the original file.
|
|
78
|
+
*
|
|
79
|
+
* @param filePath - Absolute path to the state file to load.
|
|
80
|
+
* @param validate - Optional schema validator.
|
|
81
|
+
* @param opts - Options: `backup` controls whether a backup is created on error.
|
|
82
|
+
* @returns A RecoveryResult describing the outcome.
|
|
83
|
+
*/
|
|
84
|
+
export declare function loadWithRecovery<T = unknown>(filePath: string, validate?: SchemaValidator<T>, opts?: {
|
|
85
|
+
backup?: boolean;
|
|
86
|
+
}): RecoveryResult<T>;
|
|
87
|
+
//# sourceMappingURL=state-recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-recovery.d.ts","sourceRoot":"","sources":["../../src/state/state-recovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAOzD;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,uCAAuC;IACvC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAYxD;AAWD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CA8B7E;AAiFD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAC1C,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,EAC7B,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1B,cAAc,CAAC,CAAC,CAAC,CAsFnB"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corrupt/missing state handling with recovery support.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - `loadWithRecovery()` — load a state file, reporting errors clearly
|
|
6
|
+
* with optional backup before any recovery attempt.
|
|
7
|
+
* - `backupStateFile()` — create a timestamped backup copy of a file.
|
|
8
|
+
* - `detectConflictMarkers()` — scan a file for git conflict markers and
|
|
9
|
+
* produce a report with resolution steps.
|
|
10
|
+
*
|
|
11
|
+
* Key invariant: the original file is NEVER modified or discarded.
|
|
12
|
+
* Backups are copies; the original stays intact until the user explicitly
|
|
13
|
+
* resolves the issue.
|
|
14
|
+
*
|
|
15
|
+
* Requirements: 4.6, 4.7
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync, copyFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
18
|
+
import { dirname, basename } from 'node:path';
|
|
19
|
+
import { parse } from 'yaml';
|
|
20
|
+
import { YamlLoadError } from './yaml-helpers.js';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Backup
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Create a timestamped backup of a state file.
|
|
26
|
+
*
|
|
27
|
+
* Naming convention: `<filename>.backup.<ISO-timestamp>`
|
|
28
|
+
* (colons in the timestamp are replaced with hyphens for filesystem safety).
|
|
29
|
+
*
|
|
30
|
+
* The original file is NOT modified — the backup is a pure copy.
|
|
31
|
+
*
|
|
32
|
+
* @param filePath - Absolute path to the file to back up.
|
|
33
|
+
* @returns The absolute path to the created backup file.
|
|
34
|
+
* @throws {YamlLoadError} If the source file does not exist.
|
|
35
|
+
*/
|
|
36
|
+
export function backupStateFile(filePath) {
|
|
37
|
+
if (!existsSync(filePath)) {
|
|
38
|
+
throw new YamlLoadError(filePath, 'cannot back up: file not found');
|
|
39
|
+
}
|
|
40
|
+
const timestamp = new Date().toISOString().replace(/:/g, '-');
|
|
41
|
+
const backupPath = `${filePath}.backup.${timestamp}`;
|
|
42
|
+
mkdirSync(dirname(backupPath), { recursive: true });
|
|
43
|
+
copyFileSync(filePath, backupPath);
|
|
44
|
+
return backupPath;
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Conflict detection (Req 4.6)
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
/** Git conflict marker patterns. */
|
|
50
|
+
const CONFLICT_START = /^<{7}\s/;
|
|
51
|
+
const CONFLICT_SEPARATOR = /^={7}$/;
|
|
52
|
+
const CONFLICT_END = /^>{7}\s/;
|
|
53
|
+
/**
|
|
54
|
+
* Detect git conflict markers in a file and produce a resolution report.
|
|
55
|
+
*
|
|
56
|
+
* Scans the file content for `<<<<<<<`, `=======`, `>>>>>>>` marker lines.
|
|
57
|
+
* If any are found, attempts to identify which YAML fields are in conflict
|
|
58
|
+
* by parsing the "ours" and "theirs" sections.
|
|
59
|
+
*
|
|
60
|
+
* @param filePath - Absolute path to the file to check.
|
|
61
|
+
* @returns A ConflictReport if markers are found, null otherwise.
|
|
62
|
+
*/
|
|
63
|
+
export function detectConflictMarkers(filePath) {
|
|
64
|
+
let raw;
|
|
65
|
+
try {
|
|
66
|
+
raw = readFileSync(filePath, 'utf8');
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
const code = err.code;
|
|
70
|
+
if (code === 'ENOENT') {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
const lines = raw.split('\n');
|
|
76
|
+
const hasConflicts = lines.some((line) => CONFLICT_START.test(line) || CONFLICT_END.test(line));
|
|
77
|
+
if (!hasConflicts) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
// Extract conflicting fields by parsing ours/theirs sections
|
|
81
|
+
const conflictingFields = extractConflictingFields(lines);
|
|
82
|
+
const resolutionSteps = buildResolutionSteps(filePath, conflictingFields);
|
|
83
|
+
return {
|
|
84
|
+
filePath,
|
|
85
|
+
conflictingFields,
|
|
86
|
+
resolutionSteps,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Extract field names from conflict regions by parsing the ours/theirs sections.
|
|
91
|
+
*/
|
|
92
|
+
function extractConflictingFields(lines) {
|
|
93
|
+
const fields = new Set();
|
|
94
|
+
let inOurs = false;
|
|
95
|
+
let inTheirs = false;
|
|
96
|
+
const oursLines = [];
|
|
97
|
+
const theirsLines = [];
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
if (CONFLICT_START.test(line)) {
|
|
100
|
+
inOurs = true;
|
|
101
|
+
inTheirs = false;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (CONFLICT_SEPARATOR.test(line)) {
|
|
105
|
+
inOurs = false;
|
|
106
|
+
inTheirs = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (CONFLICT_END.test(line)) {
|
|
110
|
+
inTheirs = false;
|
|
111
|
+
// Attempt to extract field names from collected sections
|
|
112
|
+
extractFieldNames(oursLines, fields);
|
|
113
|
+
extractFieldNames(theirsLines, fields);
|
|
114
|
+
oursLines.length = 0;
|
|
115
|
+
theirsLines.length = 0;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (inOurs)
|
|
119
|
+
oursLines.push(line);
|
|
120
|
+
if (inTheirs)
|
|
121
|
+
theirsLines.push(line);
|
|
122
|
+
}
|
|
123
|
+
return [...fields];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Extract top-level YAML field names from a set of lines.
|
|
127
|
+
*/
|
|
128
|
+
function extractFieldNames(lines, fields) {
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
// Match top-level YAML keys: `key:` at the start of a line (no leading whitespace)
|
|
131
|
+
const match = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):/);
|
|
132
|
+
if (match) {
|
|
133
|
+
fields.add(match[1]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build human-readable resolution steps based on detected conflicts.
|
|
139
|
+
*/
|
|
140
|
+
function buildResolutionSteps(filePath, fields) {
|
|
141
|
+
const fileName = basename(filePath);
|
|
142
|
+
const steps = [];
|
|
143
|
+
steps.push(`Open ${fileName} in an editor`);
|
|
144
|
+
if (fields.length > 0) {
|
|
145
|
+
steps.push(`Resolve conflicts in field(s): ${fields.join(', ')} — choose the correct value for each`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
steps.push('Identify the correct value for each conflicted section');
|
|
149
|
+
}
|
|
150
|
+
steps.push('Remove all conflict markers (lines starting with <<<<<<<, =======, >>>>>>>)');
|
|
151
|
+
steps.push('Validate the file is valid YAML after edits');
|
|
152
|
+
steps.push('Stage the resolved file with git add');
|
|
153
|
+
return steps;
|
|
154
|
+
}
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Recovery-aware load (Req 4.7)
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
/**
|
|
159
|
+
* Load a state file with recovery support.
|
|
160
|
+
*
|
|
161
|
+
* Behavior:
|
|
162
|
+
* - If the file is missing: returns an error result naming the file.
|
|
163
|
+
* - If the file has conflict markers: returns an error result describing
|
|
164
|
+
* the conflicts, optionally backs up before returning.
|
|
165
|
+
* - If the file is corrupt (invalid YAML or fails validation):
|
|
166
|
+
* a) Reports which file is invalid and why.
|
|
167
|
+
* b) Optionally creates a backup copy before returning.
|
|
168
|
+
* c) NEVER modifies or discards the original file.
|
|
169
|
+
*
|
|
170
|
+
* @param filePath - Absolute path to the state file to load.
|
|
171
|
+
* @param validate - Optional schema validator.
|
|
172
|
+
* @param opts - Options: `backup` controls whether a backup is created on error.
|
|
173
|
+
* @returns A RecoveryResult describing the outcome.
|
|
174
|
+
*/
|
|
175
|
+
export function loadWithRecovery(filePath, validate, opts) {
|
|
176
|
+
const shouldBackup = opts?.backup ?? false;
|
|
177
|
+
// Check if file exists
|
|
178
|
+
if (!existsSync(filePath)) {
|
|
179
|
+
return {
|
|
180
|
+
data: null,
|
|
181
|
+
recovered: false,
|
|
182
|
+
backupPath: null,
|
|
183
|
+
error: `${filePath}: file not found`,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// Read file content
|
|
187
|
+
let raw;
|
|
188
|
+
try {
|
|
189
|
+
raw = readFileSync(filePath, 'utf8');
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
return {
|
|
193
|
+
data: null,
|
|
194
|
+
recovered: false,
|
|
195
|
+
backupPath: null,
|
|
196
|
+
error: `${filePath}: unable to read file: ${err.message}`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// Check for conflict markers
|
|
200
|
+
const conflictReport = detectConflictMarkersFromContent(raw, filePath);
|
|
201
|
+
if (conflictReport) {
|
|
202
|
+
let backupPath = null;
|
|
203
|
+
if (shouldBackup) {
|
|
204
|
+
backupPath = backupStateFile(filePath);
|
|
205
|
+
}
|
|
206
|
+
const fieldInfo = conflictReport.conflictingFields.length > 0
|
|
207
|
+
? ` in fields: ${conflictReport.conflictingFields.join(', ')}`
|
|
208
|
+
: '';
|
|
209
|
+
return {
|
|
210
|
+
data: null,
|
|
211
|
+
recovered: shouldBackup,
|
|
212
|
+
backupPath,
|
|
213
|
+
error: `${filePath}: git conflict markers detected${fieldInfo}`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// Parse YAML
|
|
217
|
+
let data;
|
|
218
|
+
try {
|
|
219
|
+
data = parse(raw);
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
let backupPath = null;
|
|
223
|
+
if (shouldBackup) {
|
|
224
|
+
backupPath = backupStateFile(filePath);
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
data: null,
|
|
228
|
+
recovered: shouldBackup,
|
|
229
|
+
backupPath,
|
|
230
|
+
error: `${filePath}: YAML parse error: ${err.message}`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
// Validate schema
|
|
234
|
+
if (validate) {
|
|
235
|
+
const validationError = validate(data);
|
|
236
|
+
if (validationError) {
|
|
237
|
+
let backupPath = null;
|
|
238
|
+
if (shouldBackup) {
|
|
239
|
+
backupPath = backupStateFile(filePath);
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
data: null,
|
|
243
|
+
recovered: shouldBackup,
|
|
244
|
+
backupPath,
|
|
245
|
+
error: `${filePath}: validation failed: ${validationError}`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Success
|
|
250
|
+
return {
|
|
251
|
+
data: data,
|
|
252
|
+
recovered: false,
|
|
253
|
+
backupPath: null,
|
|
254
|
+
error: null,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Internal helper: detect conflicts from already-read content
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
function detectConflictMarkersFromContent(content, filePath) {
|
|
261
|
+
const lines = content.split('\n');
|
|
262
|
+
const hasConflicts = lines.some((line) => CONFLICT_START.test(line) || CONFLICT_END.test(line));
|
|
263
|
+
if (!hasConflicts) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
const conflictingFields = extractConflictingFields(lines);
|
|
267
|
+
const resolutionSteps = buildResolutionSteps(filePath, conflictingFields);
|
|
268
|
+
return {
|
|
269
|
+
filePath,
|
|
270
|
+
conflictingFields,
|
|
271
|
+
resolutionSteps,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=state-recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-recovery.js","sourceRoot":"","sources":["../../src/state/state-recovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only NDJSON transition log writer and reader.
|
|
3
|
+
*
|
|
4
|
+
* The transitions.log file stores one JSON record per line (NDJSON format).
|
|
5
|
+
* Each record is a TransitionRecord capturing a phase transition event.
|
|
6
|
+
*
|
|
7
|
+
* Write semantics:
|
|
8
|
+
* - Uses O_WRONLY | O_APPEND | O_CREAT flags for atomic appends.
|
|
9
|
+
* - Concurrent appends from separate processes cannot corrupt existing lines.
|
|
10
|
+
*
|
|
11
|
+
* Read semantics:
|
|
12
|
+
* - File not found → empty array (no error).
|
|
13
|
+
* - Empty lines are skipped.
|
|
14
|
+
* - Malformed JSON lines are skipped with a warning to stderr.
|
|
15
|
+
*
|
|
16
|
+
* Requirements: 4.3, 4.4, 11.5
|
|
17
|
+
*/
|
|
18
|
+
import type { TransitionRecord } from '../core/types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Append a single transition record to the NDJSON log file.
|
|
21
|
+
*
|
|
22
|
+
* Creates the file (and parent directories) if it does not exist.
|
|
23
|
+
* Uses O_APPEND to guarantee atomic line appends even under concurrent writes.
|
|
24
|
+
*
|
|
25
|
+
* @param logPath - Absolute or relative path to the transitions.log file.
|
|
26
|
+
* @param record - The transition record to append.
|
|
27
|
+
*/
|
|
28
|
+
export declare function appendTransition(logPath: string, record: TransitionRecord): void;
|
|
29
|
+
/**
|
|
30
|
+
* Read all transition records from the NDJSON log file.
|
|
31
|
+
*
|
|
32
|
+
* - Returns an empty array if the file does not exist.
|
|
33
|
+
* - Skips empty lines.
|
|
34
|
+
* - Skips malformed JSON lines (logs a warning to stderr).
|
|
35
|
+
*
|
|
36
|
+
* @param logPath - Absolute or relative path to the transitions.log file.
|
|
37
|
+
* @returns Array of parsed TransitionRecord entries.
|
|
38
|
+
*/
|
|
39
|
+
export declare function readTransitions(logPath: string): TransitionRecord[];
|
|
40
|
+
//# sourceMappingURL=transition-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transition-log.d.ts","sourceRoot":"","sources":["../../src/state/transition-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAWH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAMzD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAShF;AAMD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CA6BnE"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only NDJSON transition log writer and reader.
|
|
3
|
+
*
|
|
4
|
+
* The transitions.log file stores one JSON record per line (NDJSON format).
|
|
5
|
+
* Each record is a TransitionRecord capturing a phase transition event.
|
|
6
|
+
*
|
|
7
|
+
* Write semantics:
|
|
8
|
+
* - Uses O_WRONLY | O_APPEND | O_CREAT flags for atomic appends.
|
|
9
|
+
* - Concurrent appends from separate processes cannot corrupt existing lines.
|
|
10
|
+
*
|
|
11
|
+
* Read semantics:
|
|
12
|
+
* - File not found → empty array (no error).
|
|
13
|
+
* - Empty lines are skipped.
|
|
14
|
+
* - Malformed JSON lines are skipped with a warning to stderr.
|
|
15
|
+
*
|
|
16
|
+
* Requirements: 4.3, 4.4, 11.5
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync, openSync, writeSync, closeSync, constants, mkdirSync, } from 'node:fs';
|
|
19
|
+
import { dirname } from 'node:path';
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Writer
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/**
|
|
24
|
+
* Append a single transition record to the NDJSON log file.
|
|
25
|
+
*
|
|
26
|
+
* Creates the file (and parent directories) if it does not exist.
|
|
27
|
+
* Uses O_APPEND to guarantee atomic line appends even under concurrent writes.
|
|
28
|
+
*
|
|
29
|
+
* @param logPath - Absolute or relative path to the transitions.log file.
|
|
30
|
+
* @param record - The transition record to append.
|
|
31
|
+
*/
|
|
32
|
+
export function appendTransition(logPath, record) {
|
|
33
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
34
|
+
const line = JSON.stringify(record) + '\n';
|
|
35
|
+
const fd = openSync(logPath, constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT);
|
|
36
|
+
try {
|
|
37
|
+
writeSync(fd, line);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
closeSync(fd);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Reader
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
/**
|
|
47
|
+
* Read all transition records from the NDJSON log file.
|
|
48
|
+
*
|
|
49
|
+
* - Returns an empty array if the file does not exist.
|
|
50
|
+
* - Skips empty lines.
|
|
51
|
+
* - Skips malformed JSON lines (logs a warning to stderr).
|
|
52
|
+
*
|
|
53
|
+
* @param logPath - Absolute or relative path to the transitions.log file.
|
|
54
|
+
* @returns Array of parsed TransitionRecord entries.
|
|
55
|
+
*/
|
|
56
|
+
export function readTransitions(logPath) {
|
|
57
|
+
let raw;
|
|
58
|
+
try {
|
|
59
|
+
raw = readFileSync(logPath, 'utf8');
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const code = err.code;
|
|
63
|
+
if (code === 'ENOENT') {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
const lines = raw.split('\n');
|
|
69
|
+
const records = [];
|
|
70
|
+
for (let i = 0; i < lines.length; i++) {
|
|
71
|
+
const line = lines[i].trim();
|
|
72
|
+
if (line === '')
|
|
73
|
+
continue;
|
|
74
|
+
try {
|
|
75
|
+
records.push(JSON.parse(line));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
process.stderr.write(`transitions.log: skipping malformed line ${i + 1}: ${line.slice(0, 80)}\n`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return records;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=transition-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transition-log.js","sourceRoot":"","sources":["../../src/state/transition-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,MAAwB;IACxE,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC3C,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAC1F,IAAI,CAAC;QACH,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,OAAO,GAAuB,EAAE,CAAC;IAEvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,IAAI,KAAK,EAAE;YAAE,SAAS;QAE1B,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAqB,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,4CAA4C,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAC5E,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|