bitacora-cli 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SK3L3TØR
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ Bitacora is a deterministic project memory CLI for agent-driven workflows. It stores project context in files, validates structure, and keeps state rebuildable.
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm i -g bitacora-cli
7
+ ```
8
+
9
+ ## Usage
10
+
11
+ Show help:
12
+
13
+ ```bash
14
+ bitacora --help
15
+ ```
16
+
17
+ Typical flow:
18
+
19
+ ```bash
20
+ # init bitacora files structure and add SKILL
21
+ bitacora init
22
+
23
+ # ask your agent to complete bitacora
24
+ # whith your project info. You can
25
+ # complete bitacora by yourself if you wish.
26
+ codex "$bitacora read and fill bitacora template with this project info"
27
+
28
+ # plan some tasks
29
+ codex (planner )"$bitacora plan the unit test for this repo and add separated tasks to bitacora"
30
+
31
+ # use another agent
32
+ codex "$bitacora implement the 003 track"
33
+ ```
34
+
35
+ Use `--root <path>` on any command to target a different project directory.
36
+
37
+ ## What Each Bitacora File Stores
38
+
39
+ - `bitacora/index.md`: memory map and session read order.
40
+ - `bitacora/product.md`: project goals, scope, constraints, and non-goals.
41
+ - `bitacora/tech-stack.md`: runtime/tooling/dependencies and technical rules.
42
+ - `bitacora/workflow.md`: development method, quality gates, and handoff checklist.
43
+ - `bitacora/ux-style-guide.md`: visual tokens and UX interaction rules.
44
+ - `bitacora/tracks/tracks.md`: canonical track registry, current snapshot, and handoff summary.
45
+ - `bitacora/tracks/TRACK-*/track.md`: per-track plan, tasks, decisions, and timestamped log.
46
+ - `.agents/skills/bitacora/SKILL.md`: instructions agents follow to keep the memory system updated.
47
+
48
+ ## How Agents Read and Update Bitacora
49
+
50
+ Recommended memory read order for agents:
51
+
52
+ 1. `bitacora/index.md`
53
+ 2. `bitacora/product.md`
54
+ 3. `bitacora/tech-stack.md`
55
+ 4. `bitacora/workflow.md`
56
+ 5. `bitacora/ux-style-guide.md`
57
+ 6. `bitacora/tracks/tracks.md`
58
+ 7. Active files in `bitacora/tracks/TRACK-*/track.md`
59
+
60
+ Typical agent write operations:
61
+
62
+ - `bitacora new-track`: create a new work unit.
63
+ - `bitacora log --track-id ... --message ...`: append progress updates.
64
+ - direct updates to `bitacora/tracks/TRACK-*/track.md`: tasks, decisions, execution details.
65
+ - direct updates to `bitacora/tracks/tracks.md`: canonical project status and next action.
66
+ - `bitacora validate` and `bitacora rebuild-state`: ensure memory remains valid and deterministic.
67
+
68
+ ## Agent Skill Integration
69
+
70
+ When you run `bitacora init`, Bitacora also creates an agent skill at:
71
+
72
+ ```text
73
+ .agents/skills/bitacora/SKILL.md
74
+ ```
75
+
76
+ This skill guides agents to keep memory updated during implementation:
77
+
78
+ - Read project memory before coding.
79
+ - Create and update tracks as work progresses.
80
+ - Log decisions and progress.
81
+ - Keep a consistent handoff summary in `bitacora/tracks/tracks.md`.
82
+
83
+ ## Commands
84
+
85
+ ### `bitacora init [--force] [--root <path>]`
86
+
87
+ Creates the `bitacora/` memory structure in the project root.
88
+
89
+ - `--force`: recreates memory files if they already exist.
90
+ - `--root <path>`: sets the project root (default: current directory).
91
+
92
+ ### `bitacora new-track [trackId] [--status <status>] [--priority <priority>] [--root <path>]`
93
+
94
+ Creates a new track file. If `trackId` is omitted, Bitacora picks the next sequential ID.
95
+
96
+ - `trackId`: optional explicit track ID (example: `TRACK-010`).
97
+ - `--status <status>`: sets the initial track status.
98
+ - `--priority <priority>`: sets the initial track priority.
99
+ - `--root <path>`: sets the project root.
100
+
101
+ ### `bitacora log --track-id <trackId> --message <text> [--root <path>]`
102
+
103
+ Appends a timestamped log entry to an existing track.
104
+
105
+ - `--track-id <trackId>`: required track identifier.
106
+ - `--message <text>`: required log message.
107
+ - `--root <path>`: sets the project root.
108
+
109
+ ### `bitacora validate [--json] [--root <path>]`
110
+
111
+ Validates the Bitacora file/folder structure and reports errors.
112
+
113
+ - `--json`: prints validation output as JSON.
114
+ - `--root <path>`: sets the project root.
115
+
116
+ ### `bitacora rebuild-state [--root <path>]`
117
+
118
+ Revalidates memory and confirms deterministic state can be rebuilt from tracks.
119
+
120
+ - `--root <path>`: sets the project root.
121
+
122
+ ## Exit Codes
123
+
124
+ - `0`: success
125
+ - `1`: validation or input error
126
+ - `2`: unexpected runtime error
127
+
128
+ ## Development
129
+
130
+ Internal development documentation is in `README-DEV.md`.
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createCliProgram = createCliProgram;
5
+ exports.runCli = runCli;
6
+ const commander_1 = require("commander");
7
+ const init_1 = require("./commands/init");
8
+ const log_1 = require("./commands/log");
9
+ const new_track_1 = require("./commands/new-track");
10
+ const rebuild_state_1 = require("./commands/rebuild-state");
11
+ const validate_1 = require("./commands/validate");
12
+ const COMMAND_HELP_OVERVIEW = `
13
+ Command details:
14
+ init
15
+ Creates the bitacora memory structure in the selected project root.
16
+ Options:
17
+ --force Recreate bitacora memory if it already exists.
18
+ --root <path> Project root path (default: current directory).
19
+
20
+ new-track [trackId]
21
+ Creates a new track file, automatically picking the next track id when omitted.
22
+ Options:
23
+ --status <status> Initial status for the track.
24
+ --priority <priority> Initial priority for the track.
25
+ --root <path> Project root path (default: current directory).
26
+
27
+ validate
28
+ Validates bitacora files and folders against the expected deterministic format.
29
+ Options:
30
+ --json Prints validation output as JSON.
31
+ --root <path> Project root path (default: current directory).
32
+
33
+ rebuild-state
34
+ Revalidates track memory and confirms deterministic state can be rebuilt.
35
+ Options:
36
+ --root <path> Project root path (default: current directory).
37
+
38
+ log
39
+ Appends a timestamped log entry to an existing track.
40
+ Options:
41
+ --track-id <trackId> Track identifier.
42
+ --message <text> Log entry message.
43
+ --root <path> Project root path (default: current directory).
44
+ `;
45
+ function writeLine(writer, message) {
46
+ writer(message.endsWith("\n") ? message : `${message}\n`);
47
+ }
48
+ function createCliProgram(runtime = {}, onCommandExitCode) {
49
+ const now = runtime.now ?? (() => new Date().toISOString());
50
+ const cwd = runtime.cwd ?? (() => process.cwd());
51
+ const stdout = runtime.stdout ?? ((message) => process.stdout.write(message));
52
+ const stderr = runtime.stderr ?? ((message) => process.stderr.write(message));
53
+ const program = new commander_1.Command();
54
+ program
55
+ .name("bitacora")
56
+ .description("Deterministic agent-oriented project memory system")
57
+ .configureOutput({
58
+ writeOut: (message) => writeLine(stdout, message),
59
+ writeErr: (message) => writeLine(stderr, message)
60
+ });
61
+ program
62
+ .command("init")
63
+ .description("Create bitacora memory files and folders.")
64
+ .option("--force", "delete and recreate bitacora memory")
65
+ .option("--root <path>", "project root path")
66
+ .action((options) => {
67
+ const exitCode = (0, init_1.runInitCommand)({
68
+ rootDir: options.root ?? cwd(),
69
+ force: Boolean(options.force)
70
+ }, {
71
+ now,
72
+ onError: (message) => writeLine(stderr, message)
73
+ });
74
+ onCommandExitCode?.(exitCode);
75
+ });
76
+ program
77
+ .command("new-track [trackId]")
78
+ .description("Create a new track with optional id, status, and priority.")
79
+ .option("--status <status>", "track status")
80
+ .option("--priority <priority>", "track priority")
81
+ .option("--root <path>", "project root path")
82
+ .action((trackId, options) => {
83
+ const newTrackOptions = {
84
+ rootDir: options.root ?? cwd()
85
+ };
86
+ if (trackId !== undefined) {
87
+ newTrackOptions.trackId = trackId;
88
+ }
89
+ if (options.status !== undefined) {
90
+ newTrackOptions.status = options.status;
91
+ }
92
+ if (options.priority !== undefined) {
93
+ newTrackOptions.priority = options.priority;
94
+ }
95
+ const exitCode = (0, new_track_1.runNewTrackCommand)(newTrackOptions, {
96
+ now,
97
+ onError: (message) => writeLine(stderr, message)
98
+ });
99
+ onCommandExitCode?.(exitCode);
100
+ });
101
+ program
102
+ .command("validate")
103
+ .description("Validate bitacora structure and report errors.")
104
+ .option("--json", "output JSON validation result")
105
+ .option("--root <path>", "project root path")
106
+ .action((options) => {
107
+ const exitCode = (0, validate_1.runValidateCommand)({
108
+ rootDir: options.root ?? cwd(),
109
+ json: Boolean(options.json)
110
+ }, {
111
+ onOutput: (message) => writeLine(stdout, message)
112
+ });
113
+ onCommandExitCode?.(exitCode);
114
+ });
115
+ program
116
+ .command("rebuild-state")
117
+ .description("Revalidate memory and rebuild in-memory state from tracks.")
118
+ .option("--root <path>", "project root path")
119
+ .action((options) => {
120
+ const exitCode = (0, rebuild_state_1.runRebuildStateCommand)({
121
+ rootDir: options.root ?? cwd()
122
+ }, {
123
+ onOutput: (message) => writeLine(stdout, message),
124
+ onError: (message) => writeLine(stderr, message)
125
+ });
126
+ onCommandExitCode?.(exitCode);
127
+ });
128
+ program
129
+ .command("log")
130
+ .description("Append a log entry to a track.")
131
+ .requiredOption("--track-id <trackId>", "track identifier")
132
+ .requiredOption("--message <text>", "log message")
133
+ .option("--root <path>", "project root path")
134
+ .action((options) => {
135
+ const exitCode = (0, log_1.runLogCommand)({
136
+ rootDir: options.root ?? cwd(),
137
+ trackId: options.trackId,
138
+ message: options.message
139
+ }, {
140
+ now,
141
+ onError: (message) => writeLine(stderr, message)
142
+ });
143
+ onCommandExitCode?.(exitCode);
144
+ });
145
+ program.addHelpText("after", COMMAND_HELP_OVERVIEW);
146
+ return program;
147
+ }
148
+ async function runCli(argv, runtime = {}) {
149
+ let commandExitCode = 0;
150
+ const program = createCliProgram(runtime, (exitCode) => {
151
+ commandExitCode = exitCode;
152
+ });
153
+ program.exitOverride();
154
+ try {
155
+ await program.parseAsync(argv, { from: "node" });
156
+ return commandExitCode;
157
+ }
158
+ catch (error) {
159
+ if (error instanceof commander_1.CommanderError) {
160
+ return commandExitCode !== 0 ? commandExitCode : 1;
161
+ }
162
+ throw error;
163
+ }
164
+ }
165
+ if (require.main === module) {
166
+ runCli(process.argv)
167
+ .then((exitCode) => {
168
+ process.exitCode = exitCode;
169
+ })
170
+ .catch((error) => {
171
+ const message = error instanceof Error ? error.message : String(error);
172
+ process.stderr.write(`${message}\n`);
173
+ process.exitCode = 2;
174
+ });
175
+ }
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runInitCommand = runInitCommand;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_crypto_1 = require("node:crypto");
10
+ const templates_1 = require("../core/templates");
11
+ function computeHash(content) {
12
+ return (0, node_crypto_1.createHash)("sha256").update(content).digest("hex");
13
+ }
14
+ function readSkillsLockFile(filePath) {
15
+ if (!node_fs_1.default.existsSync(filePath)) {
16
+ return {
17
+ version: 1,
18
+ skills: {}
19
+ };
20
+ }
21
+ try {
22
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(filePath, "utf8"));
23
+ if (typeof parsed !== "object" || parsed === null) {
24
+ return { version: 1, skills: {} };
25
+ }
26
+ const versionRaw = parsed.version;
27
+ const skillsRaw = parsed.skills;
28
+ const normalized = {};
29
+ if (typeof skillsRaw === "object" && skillsRaw !== null) {
30
+ for (const [name, value] of Object.entries(skillsRaw)) {
31
+ if (typeof value !== "object" || value === null) {
32
+ continue;
33
+ }
34
+ const source = value.source;
35
+ const sourceType = value.sourceType;
36
+ const computedHash = value.computedHash;
37
+ if (typeof source === "string" &&
38
+ typeof sourceType === "string" &&
39
+ typeof computedHash === "string") {
40
+ normalized[name] = { source, sourceType, computedHash };
41
+ }
42
+ }
43
+ }
44
+ return {
45
+ version: typeof versionRaw === "number" ? versionRaw : 1,
46
+ skills: normalized
47
+ };
48
+ }
49
+ catch {
50
+ return {
51
+ version: 1,
52
+ skills: {}
53
+ };
54
+ }
55
+ }
56
+ function writeSkillsLockFile(rootDir, skillContent) {
57
+ const lockPath = node_path_1.default.join(rootDir, "skills-lock.json");
58
+ const lock = readSkillsLockFile(lockPath);
59
+ lock.skills.bitacora = {
60
+ source: ".agents/skills/bitacora/SKILL.md",
61
+ sourceType: "local",
62
+ computedHash: computeHash(skillContent)
63
+ };
64
+ node_fs_1.default.writeFileSync(lockPath, `${JSON.stringify(lock, null, 2)}\n`, "utf8");
65
+ }
66
+ function runInitCommand(options, dependencies = {}) {
67
+ const now = dependencies.now ?? (() => new Date().toISOString());
68
+ const onError = dependencies.onError ?? (() => { });
69
+ const memoryRoot = node_path_1.default.join(options.rootDir, "bitacora");
70
+ if (node_fs_1.default.existsSync(memoryRoot)) {
71
+ if (!options.force) {
72
+ onError("bitacora already exists");
73
+ return 1;
74
+ }
75
+ node_fs_1.default.rmSync(memoryRoot, { recursive: true, force: true });
76
+ }
77
+ const createdAt = now();
78
+ const createdOnDate = createdAt.slice(0, 10);
79
+ const trackId = "TRACK-001";
80
+ node_fs_1.default.mkdirSync(node_path_1.default.join(memoryRoot, "tracks", trackId), { recursive: true });
81
+ node_fs_1.default.mkdirSync(node_path_1.default.join(options.rootDir, ".agents", "skills", "bitacora"), { recursive: true });
82
+ node_fs_1.default.writeFileSync(node_path_1.default.join(memoryRoot, "product.md"), (0, templates_1.createProductTemplate)(), "utf8");
83
+ node_fs_1.default.writeFileSync(node_path_1.default.join(memoryRoot, "tech-stack.md"), (0, templates_1.createTechStackTemplate)(), "utf8");
84
+ node_fs_1.default.writeFileSync(node_path_1.default.join(memoryRoot, "workflow.md"), (0, templates_1.createWorkflowTemplate)(), "utf8");
85
+ node_fs_1.default.writeFileSync(node_path_1.default.join(memoryRoot, "ux-style-guide.md"), (0, templates_1.createUxStyleGuideTemplate)(), "utf8");
86
+ node_fs_1.default.writeFileSync(node_path_1.default.join(memoryRoot, "index.md"), (0, templates_1.createIndexTemplate)(), "utf8");
87
+ const skillContent = (0, templates_1.createAgentSkillTemplate)();
88
+ node_fs_1.default.writeFileSync(node_path_1.default.join(options.rootDir, ".agents", "skills", "bitacora", "SKILL.md"), skillContent, "utf8");
89
+ writeSkillsLockFile(options.rootDir, skillContent);
90
+ node_fs_1.default.writeFileSync(node_path_1.default.join(memoryRoot, "tracks", "tracks.md"), (0, templates_1.createTracksRegistryTemplate)(createdOnDate), "utf8");
91
+ node_fs_1.default.writeFileSync(node_path_1.default.join(memoryRoot, "tracks", "tracks-template.md"), (0, templates_1.createTracksTemplate)(), "utf8");
92
+ node_fs_1.default.writeFileSync(node_path_1.default.join(memoryRoot, "tracks", trackId, "track.md"), (0, templates_1.createTrackTemplate)(trackId, "active", "medium", createdAt), "utf8");
93
+ return 0;
94
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runLogCommand = runLogCommand;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const parser_1 = require("../core/parser");
10
+ function getTrackDirectories(rootDir) {
11
+ const tracksRoot = node_path_1.default.join(rootDir, "bitacora", "tracks");
12
+ if (!node_fs_1.default.existsSync(tracksRoot)) {
13
+ return [];
14
+ }
15
+ return node_fs_1.default
16
+ .readdirSync(tracksRoot, { withFileTypes: true })
17
+ .filter((entry) => entry.isDirectory())
18
+ .map((entry) => entry.name)
19
+ .sort((left, right) => left.localeCompare(right));
20
+ }
21
+ function runLogCommand(options, dependencies = {}) {
22
+ const now = dependencies.now ?? (() => new Date().toISOString());
23
+ const onError = dependencies.onError ?? (() => { });
24
+ const trackPath = node_path_1.default.join(options.rootDir, "bitacora", "tracks", options.trackId, "track.md");
25
+ if (!node_fs_1.default.existsSync(trackPath)) {
26
+ onError(`Track not found: ${options.trackId}`);
27
+ return 1;
28
+ }
29
+ const timestamp = now();
30
+ const normalized = node_fs_1.default.readFileSync(trackPath, "utf8").replace(/\r\n/g, "\n");
31
+ try {
32
+ (0, parser_1.parseTrackMarkdown)(normalized);
33
+ }
34
+ catch (error) {
35
+ const message = error instanceof Error ? error.message : String(error);
36
+ onError(message);
37
+ return 1;
38
+ }
39
+ const updatedHeader = normalized.replace(/^updated_at:\s*.+$/m, `updated_at: ${timestamp}`);
40
+ const line = `- ${timestamp} | ${options.message}`;
41
+ const finalContent = updatedHeader.endsWith("\n")
42
+ ? `${updatedHeader}${line}\n`
43
+ : `${updatedHeader}\n${line}\n`;
44
+ node_fs_1.default.writeFileSync(trackPath, finalContent, "utf8");
45
+ return 0;
46
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runNewTrackCommand = runNewTrackCommand;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const templates_1 = require("../core/templates");
10
+ const validator_1 = require("../core/validator");
11
+ const TRACK_ID_REGEX = /^TRACK-\d{3}$/;
12
+ function getTrackDirectories(rootDir) {
13
+ const tracksRoot = node_path_1.default.join(rootDir, "bitacora", "tracks");
14
+ if (!node_fs_1.default.existsSync(tracksRoot)) {
15
+ return [];
16
+ }
17
+ return node_fs_1.default
18
+ .readdirSync(tracksRoot, { withFileTypes: true })
19
+ .filter((entry) => entry.isDirectory())
20
+ .map((entry) => entry.name)
21
+ .sort((left, right) => left.localeCompare(right));
22
+ }
23
+ function computeNextTrackId(trackDirectories) {
24
+ const lastTrackId = trackDirectories.at(-1);
25
+ if (!lastTrackId) {
26
+ return "TRACK-001";
27
+ }
28
+ const trackNumber = Number.parseInt(lastTrackId.slice("TRACK-".length), 10);
29
+ const nextTrackNumber = Number.isFinite(trackNumber) ? trackNumber + 1 : 1;
30
+ return `TRACK-${String(nextTrackNumber).padStart(3, "0")}`;
31
+ }
32
+ function runNewTrackCommand(options, dependencies = {}) {
33
+ const now = dependencies.now ?? (() => new Date().toISOString());
34
+ const onError = dependencies.onError ?? (() => { });
35
+ const validation = (0, validator_1.validateMemory)(options.rootDir);
36
+ if (!validation.ok) {
37
+ onError(`Memory structure is invalid: ${validation.errors.join("; ")}`);
38
+ return 1;
39
+ }
40
+ const trackDirectories = getTrackDirectories(options.rootDir);
41
+ const nextTrackId = computeNextTrackId(trackDirectories);
42
+ const desiredTrackId = options.trackId ?? nextTrackId;
43
+ if (!TRACK_ID_REGEX.test(desiredTrackId)) {
44
+ onError("Invalid track ID format. Expected TRACK-###");
45
+ return 1;
46
+ }
47
+ if (options.trackId && options.trackId !== nextTrackId) {
48
+ onError(`Explicit track ID must match next sequential ID: ${nextTrackId}`);
49
+ return 1;
50
+ }
51
+ const trackDir = node_path_1.default.join(options.rootDir, "bitacora", "tracks", desiredTrackId);
52
+ if (node_fs_1.default.existsSync(trackDir)) {
53
+ onError(`Track already exists: ${desiredTrackId}`);
54
+ return 1;
55
+ }
56
+ const status = options.status ?? "active";
57
+ const priority = options.priority ?? "medium";
58
+ const timestamp = now();
59
+ node_fs_1.default.mkdirSync(trackDir, { recursive: true });
60
+ node_fs_1.default.writeFileSync(node_path_1.default.join(trackDir, "track.md"), (0, templates_1.createTrackTemplate)(desiredTrackId, status, priority, timestamp), "utf8");
61
+ return 0;
62
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runRebuildStateCommand = runRebuildStateCommand;
4
+ const validator_1 = require("../core/validator");
5
+ function runRebuildStateCommand(options, dependencies = {}) {
6
+ const onOutput = dependencies.onOutput ?? (() => { });
7
+ const onError = dependencies.onError ?? (() => { });
8
+ const validation = (0, validator_1.validateMemory)(options.rootDir);
9
+ if (!validation.ok) {
10
+ onError(`Memory structure is invalid: ${validation.errors.join("; ")}`);
11
+ return 1;
12
+ }
13
+ onOutput("State rebuilt");
14
+ return 0;
15
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runValidateCommand = runValidateCommand;
4
+ const validator_1 = require("../core/validator");
5
+ function runValidateCommand(options, dependencies = {}) {
6
+ const onOutput = dependencies.onOutput ?? (() => { });
7
+ const result = (0, validator_1.validateMemory)(options.rootDir);
8
+ if (options.json) {
9
+ onOutput(`${JSON.stringify(result, null, 2)}\n`);
10
+ return result.ok ? 0 : 1;
11
+ }
12
+ if (result.ok) {
13
+ onOutput("Validation passed");
14
+ return 0;
15
+ }
16
+ onOutput("Validation failed");
17
+ for (const error of result.errors) {
18
+ onOutput(error);
19
+ }
20
+ return 1;
21
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.boot = boot;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const state_builder_1 = require("./state-builder");
10
+ const validator_1 = require("./validator");
11
+ function readRequiredFile(rootDir, relativePath) {
12
+ const targetPath = node_path_1.default.join(rootDir, relativePath);
13
+ if (!node_fs_1.default.existsSync(targetPath)) {
14
+ throw new Error("Memory structure is invalid");
15
+ }
16
+ return node_fs_1.default.readFileSync(targetPath, "utf8");
17
+ }
18
+ function boot(rootDir, loaders = {}) {
19
+ const index = readRequiredFile(rootDir, node_path_1.default.join("bitacora", "index.md"));
20
+ const product = readRequiredFile(rootDir, node_path_1.default.join("bitacora", "product.md"));
21
+ const techStack = readRequiredFile(rootDir, node_path_1.default.join("bitacora", "tech-stack.md"));
22
+ const workflow = readRequiredFile(rootDir, node_path_1.default.join("bitacora", "workflow.md"));
23
+ const uxStyleGuide = readRequiredFile(rootDir, node_path_1.default.join("bitacora", "ux-style-guide.md"));
24
+ loaders.loadContext?.({ index, product, techStack, workflow, uxStyleGuide });
25
+ const tracksRoot = node_path_1.default.join(rootDir, "bitacora", "tracks");
26
+ if (!node_fs_1.default.existsSync(tracksRoot)) {
27
+ throw new Error("Memory structure is invalid");
28
+ }
29
+ const trackFiles = node_fs_1.default
30
+ .readdirSync(tracksRoot, { withFileTypes: true })
31
+ .filter((entry) => entry.isDirectory())
32
+ .map((entry) => node_path_1.default.join(tracksRoot, entry.name, "track.md"))
33
+ .sort((left, right) => left.localeCompare(right));
34
+ loaders.loadTracks?.(trackFiles);
35
+ const validation = (0, validator_1.validateMemory)(rootDir);
36
+ if (!validation.ok) {
37
+ throw new Error(`Memory structure is invalid: ${validation.errors.join("; ")}`);
38
+ }
39
+ return (0, state_builder_1.buildStateFromTracks)(validation.tracks);
40
+ }
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTrackMarkdown = parseTrackMarkdown;
4
+ const types_1 = require("../types");
5
+ function parseFrontmatterBlock(markdown) {
6
+ const normalized = markdown.replace(/\r\n/g, "\n");
7
+ const parts = normalized.split("\n");
8
+ if (parts[0] !== "---") {
9
+ throw new Error("Missing frontmatter");
10
+ }
11
+ const endIndex = parts.indexOf("---", 1);
12
+ if (endIndex === -1) {
13
+ throw new Error("Missing frontmatter");
14
+ }
15
+ const raw = {};
16
+ for (const line of parts.slice(1, endIndex)) {
17
+ if (line.trim() === "") {
18
+ continue;
19
+ }
20
+ const separatorIndex = line.indexOf(":");
21
+ if (separatorIndex === -1) {
22
+ continue;
23
+ }
24
+ const key = line.slice(0, separatorIndex).trim();
25
+ const value = line.slice(separatorIndex + 1).trim();
26
+ raw[key] = value;
27
+ }
28
+ return raw;
29
+ }
30
+ function parseSections(markdown) {
31
+ const normalized = markdown.replace(/\r\n/g, "\n");
32
+ const parts = normalized.split("\n---\n");
33
+ const body = parts.length > 1 ? parts.slice(1).join("\n---\n") : "";
34
+ const sectionMap = {};
35
+ const headings = ["Overview", "Tasks", "Decisions", "Log"];
36
+ for (let index = 0; index < headings.length; index += 1) {
37
+ const heading = headings[index];
38
+ if (heading === undefined) {
39
+ continue;
40
+ }
41
+ const startToken = `# ${heading}\n`;
42
+ const startIndex = body.indexOf(startToken);
43
+ if (startIndex === -1) {
44
+ continue;
45
+ }
46
+ const contentStart = startIndex + startToken.length;
47
+ const nextHeading = headings[index + 1];
48
+ const endIndex = nextHeading === undefined
49
+ ? body.length
50
+ : body.indexOf(`# ${nextHeading}\n`, contentStart);
51
+ const content = body.slice(contentStart, endIndex === -1 ? body.length : endIndex).trim();
52
+ sectionMap[heading.toLowerCase()] = content;
53
+ }
54
+ return sectionMap;
55
+ }
56
+ function parseTrackMarkdown(markdown) {
57
+ const frontmatter = parseFrontmatterBlock(markdown);
58
+ const sections = parseSections(markdown);
59
+ const getRequiredField = (field) => {
60
+ const value = frontmatter[field];
61
+ if (!value) {
62
+ throw new Error(`Missing frontmatter field: ${field}`);
63
+ }
64
+ return value;
65
+ };
66
+ const requiredFrontmatterFields = [
67
+ "track_id",
68
+ "status",
69
+ "priority",
70
+ "created_at",
71
+ "updated_at"
72
+ ];
73
+ for (const field of requiredFrontmatterFields) {
74
+ getRequiredField(field);
75
+ }
76
+ const status = getRequiredField("status");
77
+ const priority = getRequiredField("priority");
78
+ const trackId = getRequiredField("track_id");
79
+ const createdAt = getRequiredField("created_at");
80
+ const updatedAt = getRequiredField("updated_at");
81
+ if (!types_1.TRACK_STATUSES.includes(status)) {
82
+ throw new Error("Invalid status");
83
+ }
84
+ if (!types_1.TRACK_PRIORITIES.includes(priority)) {
85
+ throw new Error("Invalid priority");
86
+ }
87
+ const requiredSections = ["overview", "tasks", "decisions", "log"];
88
+ for (const requiredSection of requiredSections) {
89
+ if (!(requiredSection in sections)) {
90
+ const heading = requiredSection.charAt(0).toUpperCase() + requiredSection.slice(1);
91
+ throw new Error(`Missing section: ${heading}`);
92
+ }
93
+ }
94
+ return {
95
+ frontmatter: {
96
+ track_id: trackId,
97
+ status: status,
98
+ priority: priority,
99
+ created_at: createdAt,
100
+ updated_at: updatedAt
101
+ },
102
+ sections: {
103
+ overview: sections.overview ?? "",
104
+ tasks: sections.tasks ?? "",
105
+ decisions: sections.decisions ?? "",
106
+ log: sections.log ?? ""
107
+ }
108
+ };
109
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildIndexFromTracks = buildIndexFromTracks;
7
+ exports.buildStateFromTracks = buildStateFromTracks;
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ function getTrackNumber(trackId) {
10
+ const match = /^TRACK-(\d{3})$/.exec(trackId);
11
+ if (!match) {
12
+ return Number.MAX_SAFE_INTEGER;
13
+ }
14
+ const numericPart = match[1];
15
+ if (!numericPart) {
16
+ return Number.MAX_SAFE_INTEGER;
17
+ }
18
+ return Number.parseInt(numericPart, 10);
19
+ }
20
+ function sortTrackEntries(entries) {
21
+ return [...entries].sort((left, right) => getTrackNumber(left.track_id) - getTrackNumber(right.track_id));
22
+ }
23
+ function buildIndexFromTracks(tracks) {
24
+ const entries = tracks.map((track) => ({
25
+ track_id: track.frontmatter.track_id,
26
+ status: track.frontmatter.status,
27
+ priority: track.frontmatter.priority,
28
+ created_at: track.frontmatter.created_at,
29
+ updated_at: track.frontmatter.updated_at,
30
+ path: node_path_1.default.join("bitacora", "tracks", track.directoryName, "track.md").replace(/\\/g, "/")
31
+ }));
32
+ return sortTrackEntries(entries);
33
+ }
34
+ function buildStateFromTracks(tracks) {
35
+ const indexEntries = buildIndexFromTracks(tracks);
36
+ return {
37
+ project: {
38
+ memory_root: "bitacora",
39
+ context_paths: [
40
+ "bitacora/product.md",
41
+ "bitacora/tech-stack.md",
42
+ "bitacora/workflow.md",
43
+ "bitacora/ux-style-guide.md"
44
+ ],
45
+ track_count: tracks.length
46
+ },
47
+ active_tracks: sortTrackEntries(indexEntries.filter((entry) => entry.status === "active")),
48
+ blocked_tracks: sortTrackEntries(indexEntries.filter((entry) => entry.status === "blocked")),
49
+ completed_tracks: sortTrackEntries(indexEntries.filter((entry) => entry.status === "completed"))
50
+ };
51
+ }
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createIndexTemplate = createIndexTemplate;
4
+ exports.createProductTemplate = createProductTemplate;
5
+ exports.createTechStackTemplate = createTechStackTemplate;
6
+ exports.createWorkflowTemplate = createWorkflowTemplate;
7
+ exports.createUxStyleGuideTemplate = createUxStyleGuideTemplate;
8
+ exports.createAgentSkillTemplate = createAgentSkillTemplate;
9
+ exports.createTracksRegistryTemplate = createTracksRegistryTemplate;
10
+ exports.createTracksTemplate = createTracksTemplate;
11
+ exports.createTrackTemplate = createTrackTemplate;
12
+ function createIndexTemplate() {
13
+ return `# Bitacora Index
14
+
15
+ Quick map of where to find each kind of project memory.
16
+
17
+ ## Read Order (Session Start)
18
+
19
+ 1. \`product.md\` (product goals, constraints, and scope)
20
+ 2. \`tech-stack.md\` (runtime, dependencies, and technical rules)
21
+ 3. \`workflow.md\` (execution process and mandatory handoff rules)
22
+ 4. \`ux-style-guide.md\` (visual style tokens and UX constraints)
23
+ 5. \`tracks/tracks.md\` (canonical project status and next actions)
24
+ 6. \`tracks/TRACK-*/track.md\` (details for active or relevant tracks)
25
+
26
+ ## File Index
27
+
28
+ ### Root Docs
29
+ - \`product.md\`: what the project is, why it exists, and what is in scope.
30
+ - \`tech-stack.md\`: runtime, dependencies, architecture constraints, and technical rules.
31
+ - \`workflow.md\`: TDD process, quality gates, and mandatory handoff rules.
32
+ - \`ux-style-guide.md\`: colors, typography, spacing, and interaction style rules.
33
+
34
+ ### \`tracks/\`
35
+ - \`tracks/tracks.md\`: canonical project status, registry, and handoff summary.
36
+ - \`tracks/tracks-template.md\`: template used when creating new tracks.
37
+ - \`tracks/TRACK-001/track.md\`: first active track created by \`bitacora init\`.
38
+ - \`tracks/TRACK-*/track.md\`: per-track execution details (overview, tasks, decisions, log).
39
+
40
+ Mandatory behavior:
41
+
42
+ - Always read this index at session start.
43
+ - Always update memory before session end.
44
+ - Always keep \`tracks/tracks.md\` aligned with track-level changes.
45
+ `;
46
+ }
47
+ function createProductTemplate() {
48
+ return `# Product
49
+
50
+ ## Name
51
+ <project name>
52
+
53
+ ## One-liner
54
+ <single sentence describing the product and core value>
55
+
56
+ ## Problem
57
+ <what pain/problem this project solves>
58
+
59
+ ## Goals
60
+ - <goal 1>
61
+ - <goal 2>
62
+
63
+ ## Non-goals
64
+ - <out of scope 1>
65
+ - <out of scope 2>
66
+
67
+ ## Success Criteria
68
+ - <measurable outcome 1>
69
+ - <measurable outcome 2>
70
+ `;
71
+ }
72
+ function createTechStackTemplate() {
73
+ return `# Tech Stack
74
+
75
+ ## Runtime
76
+ - Node.js <version>
77
+ - TypeScript
78
+
79
+ ## Tooling
80
+ - <test runner>
81
+ - <build tool>
82
+ - <dev runtime>
83
+
84
+ ## Runtime Dependencies
85
+ - <dependency>: <why it exists>
86
+
87
+ ## Core Technical Rules
88
+ - Keep core/domain modules isolated from infra adapters.
89
+ - Keep persistence and external integrations behind explicit contracts.
90
+ - Keep logs and outputs deterministic when possible.
91
+ `;
92
+ }
93
+ function createWorkflowTemplate() {
94
+ return `# Workflow
95
+
96
+ ## Development Method
97
+ - Mandatory TDD: RED -> GREEN -> REFACTOR.
98
+ - Prefer small vertical slices with deterministic verification.
99
+
100
+ ## Implementation Rules
101
+ - Start from domain/core behavior before adapters when feasible.
102
+ - Keep side effects isolated behind explicit interfaces.
103
+ - Keep tests focused, deterministic, and close to behavior.
104
+
105
+ ## Non-negotiable Session Rules
106
+ - Always read \`bitacora/index.md\` at the beginning of every session.
107
+ - Always update \`bitacora/tracks/tracks.md\` after meaningful implementation changes.
108
+ - Always write handoff updates before ending a session.
109
+
110
+ ## Handoff Checklist
111
+ - What changed
112
+ - Tests run (exact commands + result)
113
+ - Current TDD phase
114
+ - Blockers or assumptions
115
+ - Single best next action
116
+ `;
117
+ }
118
+ function createUxStyleGuideTemplate() {
119
+ return `# UX Style Guide
120
+
121
+ ## Visual Tokens
122
+ - Background: <hex>
123
+ - Surface: <hex>
124
+ - Surface alt: <hex>
125
+ - Text: <hex>
126
+ - Muted text: <hex>
127
+ - Primary accent: <hex>
128
+ - Success: <hex>
129
+ - Warning: <hex>
130
+ - Error: <hex>
131
+ - Border: <hex>
132
+
133
+ ## Layout / Spacing
134
+ - Base spacing unit: 4px grid.
135
+ - Panel gap: <value>.
136
+ - Panel padding: <value>.
137
+ - Header padding: <value>.
138
+
139
+ ## Borders / Depth
140
+ - Border style: <width and color>.
141
+ - Shadow style: <value>.
142
+ - Effects policy: avoid visual noise; prefer crisp, readable UI.
143
+
144
+ ## Typography
145
+ - Font family: <font stack>.
146
+ - Heading style: <size/weight>.
147
+ - Body style: <size/weight>.
148
+ - Label style: <size/weight/casing>.
149
+
150
+ ## Components / Interaction Rules
151
+ - Buttons: <primary and secondary behavior>.
152
+ - Inputs: <states and readability constraints>.
153
+ - Data views: <tables/charts/cards style constraints>.
154
+ - Feedback states: define clear success/warning/error treatment.
155
+
156
+ ## Motion / Performance
157
+ - Keep animations minimal and purposeful.
158
+ - Prefer readability and responsiveness over decoration.
159
+ - Avoid expensive full-screen effects in frequent update paths.
160
+ `;
161
+ }
162
+ function createAgentSkillTemplate() {
163
+ return `---
164
+ name: bitacora
165
+ description: Keep deterministic project memory in bitacora/ and update it continuously during implementation sessions.
166
+ version: 1.0.0
167
+ type: local
168
+ source: .agents/skills/bitacora/SKILL.md
169
+ ---
170
+
171
+ # Bitacora Skill
172
+
173
+ Use this skill to keep deterministic project memory in \`bitacora/\` and to update it continuously while implementing changes.
174
+
175
+ Canonical skill file path: \`.agents/skills/bitacora/SKILL.md\`.
176
+
177
+ ## Mandatory Session Protocol
178
+ - Always read \`bitacora/index.md\` at session start.
179
+ - Always read \`bitacora/product.md\`, \`bitacora/tech-stack.md\`, \`bitacora/workflow.md\`, and \`bitacora/ux-style-guide.md\` before making code changes.
180
+ - Always read \`bitacora/tracks/tracks.md\` and the active \`bitacora/tracks/TRACK-*/track.md\` files before implementation.
181
+ - Always write memory updates before ending the session.
182
+
183
+ ## What To Update During Work
184
+ - Update \`bitacora/tracks/TRACK-*/track.md\` while work progresses (tasks, decisions, logs).
185
+ - Update \`bitacora/tracks/tracks.md\` after meaningful changes, including current status and next action.
186
+ - Update root docs when product/tech/workflow/ux assumptions change.
187
+
188
+ ## File Map (Where To Look)
189
+ - \`bitacora/product.md\`: scope, goals, constraints, non-goals.
190
+ - \`bitacora/tech-stack.md\`: runtime, dependencies, architecture rules.
191
+ - \`bitacora/workflow.md\`: TDD rules, handoff checklist, quality gates.
192
+ - \`bitacora/ux-style-guide.md\`: visual tokens, layout, typography, interaction style.
193
+ - \`bitacora/tracks/tracks.md\`: canonical status registry and handoff summary.
194
+ - \`bitacora/tracks/tracks-template.md\`: template for new tracks.
195
+ - \`bitacora/tracks/TRACK-*/track.md\`: per-track execution details.
196
+
197
+ ## Manual Bootstrap (No CLI Required)
198
+ If the CLI is unavailable, create this exact structure manually:
199
+
200
+ \`\`\`text
201
+ bitacora/
202
+ index.md
203
+ product.md
204
+ tech-stack.md
205
+ workflow.md
206
+ ux-style-guide.md
207
+ tracks/
208
+ tracks.md
209
+ tracks-template.md
210
+ TRACK-001/
211
+ track.md
212
+ \`\`\`
213
+
214
+ Required initial metadata for \`TRACK-001\` in \`track.md\` frontmatter:
215
+ - \`track_id: TRACK-001\`
216
+ - \`status: active\`
217
+ - \`priority: medium\`
218
+ - \`created_at\` and \`updated_at\` as ISO timestamps
219
+
220
+ Required track sections in \`track.md\`:
221
+ - \`# Overview\`
222
+ - \`# Tasks\`
223
+ - \`# Decisions\`
224
+ - \`# Log\`
225
+
226
+ ## End Of Session Checklist
227
+ - Confirm \`bitacora/tracks/tracks.md\` is updated.
228
+ - Confirm active \`TRACK-*/track.md\` files include latest decisions and logs.
229
+ `;
230
+ }
231
+ function createTracksRegistryTemplate(date) {
232
+ return `# Tracks
233
+
234
+ > Canonical project status and handoff registry.
235
+ >
236
+ > Last updated: ${date}
237
+ >
238
+ > Rule: update this file after meaningful implementation changes.
239
+
240
+ ## Snapshot
241
+
242
+ - Project: <project-name>
243
+ - Current status: TRACK-001 initialized
244
+ - Active tracks: TRACK-001
245
+
246
+ ## Track Registry
247
+
248
+ | ID | Title | Status | Phase | Last Update | Notes |
249
+ | --- | --- | --- | --- | --- | --- |
250
+ | TRACK-001 | Bootstrap memory | active | red | ${date} | Initial track created by \`bitacora init\` |
251
+
252
+ ## Session Handoff (Required)
253
+
254
+ After each substantial change, update:
255
+
256
+ 1. This file (\`tracks/tracks.md\`).
257
+ 2. Affected \`tracks/TRACK-*/track.md\` files.
258
+
259
+ Minimum handoff payload:
260
+ - Track(s) touched
261
+ - Tests run (exact command + result)
262
+ - Current TDD phase
263
+ - Blockers/assumptions
264
+ - Next recommended action
265
+ `;
266
+ }
267
+ function createTracksTemplate() {
268
+ return `# Tracks Template
269
+
270
+ Use this template when creating a new track.
271
+
272
+ ## Header
273
+
274
+ - Track ID: \`TRACK-XYZ\`
275
+ - Title: <short title>
276
+ - Status: \`active | blocked | completed | archived\`
277
+ - Priority: \`low | medium | high\`
278
+ - Updated at: \`YYYY-MM-DDTHH:mm:ss.sssZ\`
279
+
280
+ ## Overview
281
+
282
+ - Goal: <one sentence>
283
+ - Scope:
284
+ - <item>
285
+ - <item>
286
+ - Out of scope:
287
+ - <item>
288
+
289
+ ## Tasks
290
+
291
+ - [ ] RED: <failing test>
292
+ - [ ] GREEN: <minimal implementation>
293
+ - [ ] REFACTOR: <safe cleanup>
294
+
295
+ ## Decisions
296
+
297
+ - <YYYY-MM-DD> | <decision and rationale>
298
+
299
+ ## Log
300
+
301
+ - <YYYY-MM-DDTHH:mm:ss.sssZ> | <progress update>
302
+ `;
303
+ }
304
+ function createTrackTemplate(trackId, status, priority, isoTimestamp) {
305
+ return `---
306
+ track_id: ${trackId}
307
+ status: ${status}
308
+ priority: ${priority}
309
+ created_at: ${isoTimestamp}
310
+ updated_at: ${isoTimestamp}
311
+ ---
312
+
313
+ # Overview
314
+
315
+ - Goal: <one sentence>
316
+ - Scope:
317
+ - <item>
318
+ - <item>
319
+
320
+ # Tasks
321
+
322
+ - [ ] RED: add failing test(s)
323
+ - [ ] GREEN: implement minimal passing behavior
324
+ - [ ] REFACTOR: improve without changing behavior
325
+
326
+ # Decisions
327
+
328
+ - ${isoTimestamp} | track created
329
+
330
+ # Log
331
+ - ${isoTimestamp} | track created
332
+ `;
333
+ }
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateMemory = validateMemory;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const parser_1 = require("./parser");
10
+ const TRACK_DIRECTORY_REGEX = /^TRACK-(\d{3})$/;
11
+ function getTrackDirectories(rootDir) {
12
+ const tracksRoot = node_path_1.default.join(rootDir, "bitacora", "tracks");
13
+ if (!node_fs_1.default.existsSync(tracksRoot)) {
14
+ return [];
15
+ }
16
+ return node_fs_1.default
17
+ .readdirSync(tracksRoot, { withFileTypes: true })
18
+ .filter((entry) => entry.isDirectory())
19
+ .map((entry) => entry.name)
20
+ .sort();
21
+ }
22
+ function loadTracks(rootDir, errors) {
23
+ const tracksRoot = node_path_1.default.join(rootDir, "bitacora", "tracks");
24
+ const trackDirs = getTrackDirectories(rootDir);
25
+ const tracks = [];
26
+ for (const directoryName of trackDirs) {
27
+ const filePath = node_path_1.default.join(tracksRoot, directoryName, "track.md");
28
+ if (!node_fs_1.default.existsSync(filePath)) {
29
+ errors.push(`Missing track.md: ${directoryName}`);
30
+ continue;
31
+ }
32
+ const markdown = node_fs_1.default.readFileSync(filePath, "utf8");
33
+ try {
34
+ const parsed = (0, parser_1.parseTrackMarkdown)(markdown);
35
+ tracks.push({
36
+ ...parsed,
37
+ filePath,
38
+ directoryName
39
+ });
40
+ }
41
+ catch (error) {
42
+ const message = error instanceof Error ? error.message : String(error);
43
+ errors.push(`Invalid track file ${directoryName}: ${message}`);
44
+ }
45
+ }
46
+ return tracks;
47
+ }
48
+ function validateTrackNumbering(trackDirectories, errors) {
49
+ const numericTrackIds = trackDirectories
50
+ .map((directoryName) => {
51
+ const match = TRACK_DIRECTORY_REGEX.exec(directoryName);
52
+ if (!match) {
53
+ errors.push(`Invalid track directory name: ${directoryName}`);
54
+ return null;
55
+ }
56
+ const numericPart = match[1];
57
+ if (!numericPart) {
58
+ errors.push(`Invalid track directory name: ${directoryName}`);
59
+ return null;
60
+ }
61
+ return Number.parseInt(numericPart, 10);
62
+ })
63
+ .filter((trackNumber) => trackNumber !== null)
64
+ .sort((left, right) => left - right);
65
+ for (let index = 0; index < numericTrackIds.length; index += 1) {
66
+ const expected = index + 1;
67
+ const actual = numericTrackIds[index];
68
+ if (actual !== expected) {
69
+ const expectedId = `TRACK-${String(expected).padStart(3, "0")}`;
70
+ const actualId = `TRACK-${String(actual).padStart(3, "0")}`;
71
+ errors.push(`Track numbering gap: expected ${expectedId} but found ${actualId}`);
72
+ return;
73
+ }
74
+ }
75
+ }
76
+ function validateDuplicateTrackIds(tracks, errors) {
77
+ const seen = new Set();
78
+ for (const track of tracks) {
79
+ const trackId = track.frontmatter.track_id;
80
+ if (seen.has(trackId)) {
81
+ errors.push(`Duplicate track_id: ${trackId}`);
82
+ continue;
83
+ }
84
+ seen.add(trackId);
85
+ }
86
+ }
87
+ function parseLogTimestamp(line) {
88
+ const match = /^-\s+([^|]+)\s+\|\s+.+$/.exec(line.trim());
89
+ if (!match) {
90
+ return null;
91
+ }
92
+ const timestampRaw = match[1];
93
+ if (!timestampRaw) {
94
+ return null;
95
+ }
96
+ const timestamp = Date.parse(timestampRaw.trim());
97
+ return Number.isNaN(timestamp) ? null : timestamp;
98
+ }
99
+ function validateUpdatedAtAgainstLogEntries(tracks, errors) {
100
+ for (const track of tracks) {
101
+ const updatedAt = Date.parse(track.frontmatter.updated_at);
102
+ if (Number.isNaN(updatedAt)) {
103
+ continue;
104
+ }
105
+ const latestLogTimestamp = track.sections.log
106
+ .split("\n")
107
+ .map((line) => parseLogTimestamp(line))
108
+ .filter((timestamp) => timestamp !== null)
109
+ .reduce((latest, current) => (latest === null || current > latest ? current : latest), null);
110
+ if (latestLogTimestamp !== null && updatedAt < latestLogTimestamp) {
111
+ errors.push(`updated_at is older than latest log entry for ${track.frontmatter.track_id}`);
112
+ }
113
+ }
114
+ }
115
+ function validateMemory(rootDir) {
116
+ const errors = [];
117
+ const trackDirectories = getTrackDirectories(rootDir);
118
+ validateTrackNumbering(trackDirectories, errors);
119
+ const tracks = loadTracks(rootDir, errors);
120
+ validateDuplicateTrackIds(tracks, errors);
121
+ validateUpdatedAtAgainstLogEntries(tracks, errors);
122
+ errors.sort((left, right) => left.localeCompare(right));
123
+ return {
124
+ ok: errors.length === 0,
125
+ errors,
126
+ tracks
127
+ };
128
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateMemory = exports.buildStateFromTracks = exports.buildIndexFromTracks = exports.parseTrackMarkdown = exports.boot = void 0;
4
+ var boot_1 = require("./core/boot");
5
+ Object.defineProperty(exports, "boot", { enumerable: true, get: function () { return boot_1.boot; } });
6
+ var parser_1 = require("./core/parser");
7
+ Object.defineProperty(exports, "parseTrackMarkdown", { enumerable: true, get: function () { return parser_1.parseTrackMarkdown; } });
8
+ var state_builder_1 = require("./core/state-builder");
9
+ Object.defineProperty(exports, "buildIndexFromTracks", { enumerable: true, get: function () { return state_builder_1.buildIndexFromTracks; } });
10
+ Object.defineProperty(exports, "buildStateFromTracks", { enumerable: true, get: function () { return state_builder_1.buildStateFromTracks; } });
11
+ var validator_1 = require("./core/validator");
12
+ Object.defineProperty(exports, "validateMemory", { enumerable: true, get: function () { return validator_1.validateMemory; } });
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TRACK_PRIORITIES = exports.TRACK_STATUSES = void 0;
4
+ exports.TRACK_STATUSES = ["active", "completed", "blocked", "archived"];
5
+ exports.TRACK_PRIORITIES = ["low", "medium", "high"];
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "bitacora-cli",
3
+ "version": "1.0.0",
4
+ "description": "Deterministic agent-oriented project memory system. Provides structured Markdown-based long-term memory, strict validation, state reconstruction, and an enforcement CLI with agent skill integration to guide and limit agent behavior.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/N0M4D-D3V/bitacora.git"
8
+ },
9
+ "homepage": "https://github.com/N0M4D-D3V/bitacora#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/N0M4D-D3V/bitacora/issues"
12
+ },
13
+ "keywords": [
14
+ "bitacora",
15
+ "deterministic",
16
+ "memory",
17
+ "cli",
18
+ "agent"
19
+ ],
20
+ "license": "MIT",
21
+ "author": "SK3L3TØR",
22
+ "files": [
23
+ "dist/src/**/*",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "type": "commonjs",
28
+ "main": "dist/src/index.js",
29
+ "bin": {
30
+ "bitacora": "dist/src/cli.js"
31
+ },
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.build.json",
40
+ "prepack": "npm run build",
41
+ "typecheck": "tsc -p tsconfig.json --noEmit",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "test:unit": "vitest run",
45
+ "start": "tsx src/cli.ts"
46
+ },
47
+ "dependencies": {
48
+ "commander": "^12.1.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^22.13.10",
52
+ "tsx": "^4.19.3",
53
+ "typescript": "^5.8.2",
54
+ "vitest": "^3.0.9"
55
+ }
56
+ }