@forgelore/core 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/dist/index.d.ts +213 -0
- package/dist/index.js +539 -0
- package/dist/index.js.map +1 -0
- package/package.json +37 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forgelore core types
|
|
3
|
+
* Provider-agnostic spec management types
|
|
4
|
+
*/
|
|
5
|
+
type SpecMode = "local" | "local+global" | "global";
|
|
6
|
+
interface GlobalSpecConfig {
|
|
7
|
+
source: string;
|
|
8
|
+
path: string;
|
|
9
|
+
autoSync?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface OrchestrationConfig {
|
|
12
|
+
defaultMode: "sequential" | "parallel" | "swarm";
|
|
13
|
+
maxRetries: number;
|
|
14
|
+
parallelTracks: number;
|
|
15
|
+
}
|
|
16
|
+
interface EnforcementConfig {
|
|
17
|
+
requireSpecForChanges: boolean;
|
|
18
|
+
warnOnUnspeccedEdits: boolean;
|
|
19
|
+
blockArchiveOnDrift: boolean;
|
|
20
|
+
autoInjectContext: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface ForgeloreConfig {
|
|
23
|
+
$schema?: string;
|
|
24
|
+
version: string;
|
|
25
|
+
mode: SpecMode;
|
|
26
|
+
global?: GlobalSpecConfig;
|
|
27
|
+
orchestration: OrchestrationConfig;
|
|
28
|
+
enforcement: EnforcementConfig;
|
|
29
|
+
}
|
|
30
|
+
type ChangeStatus = "proposed" | "planning" | "in-progress" | "validating" | "validated" | "archiving" | "archived";
|
|
31
|
+
type TaskStatus = "pending" | "claimed" | "in-progress" | "implemented" | "validating" | "passed" | "failed" | "blocked";
|
|
32
|
+
interface Task {
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
description: string;
|
|
36
|
+
status: TaskStatus;
|
|
37
|
+
assignedTo?: string;
|
|
38
|
+
category?: string;
|
|
39
|
+
dependencies?: string[];
|
|
40
|
+
}
|
|
41
|
+
interface Change {
|
|
42
|
+
name: string;
|
|
43
|
+
status: ChangeStatus;
|
|
44
|
+
createdAt: string;
|
|
45
|
+
updatedAt: string;
|
|
46
|
+
path: string;
|
|
47
|
+
tasks: Task[];
|
|
48
|
+
proposal?: string;
|
|
49
|
+
}
|
|
50
|
+
interface Capability {
|
|
51
|
+
id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
description: string;
|
|
54
|
+
sourceChange: string;
|
|
55
|
+
archivedAt: string;
|
|
56
|
+
files: string[];
|
|
57
|
+
tags?: string[];
|
|
58
|
+
}
|
|
59
|
+
interface Decision {
|
|
60
|
+
id: string;
|
|
61
|
+
title: string;
|
|
62
|
+
status: "proposed" | "accepted" | "deprecated" | "superseded";
|
|
63
|
+
date: string;
|
|
64
|
+
context: string;
|
|
65
|
+
decision: string;
|
|
66
|
+
consequences: string;
|
|
67
|
+
}
|
|
68
|
+
interface KnowledgeBase {
|
|
69
|
+
capabilities: Capability[];
|
|
70
|
+
decisions: Decision[];
|
|
71
|
+
architecture?: string;
|
|
72
|
+
patterns?: string;
|
|
73
|
+
glossary?: string;
|
|
74
|
+
}
|
|
75
|
+
type DriftSeverity = "info" | "warning" | "critical";
|
|
76
|
+
interface DriftItem {
|
|
77
|
+
type: "unspecced-change" | "stale-spec" | "missing-capability" | "outdated-knowledge";
|
|
78
|
+
severity: DriftSeverity;
|
|
79
|
+
file?: string;
|
|
80
|
+
spec?: string;
|
|
81
|
+
message: string;
|
|
82
|
+
}
|
|
83
|
+
interface DriftReport {
|
|
84
|
+
score: number;
|
|
85
|
+
items: DriftItem[];
|
|
86
|
+
timestamp: string;
|
|
87
|
+
}
|
|
88
|
+
type ValidationVerdict = "PASS" | "FAIL" | "NEEDS_REVIEW";
|
|
89
|
+
interface RequirementValidation {
|
|
90
|
+
requirement: string;
|
|
91
|
+
status: "pass" | "fail" | "partial";
|
|
92
|
+
evidence?: string;
|
|
93
|
+
reason?: string;
|
|
94
|
+
}
|
|
95
|
+
interface ValidationResult {
|
|
96
|
+
verdict: ValidationVerdict;
|
|
97
|
+
confidence: number;
|
|
98
|
+
requirements: RequirementValidation[];
|
|
99
|
+
issues: string[];
|
|
100
|
+
suggestions: string[];
|
|
101
|
+
}
|
|
102
|
+
interface ForgeloreProject {
|
|
103
|
+
root: string;
|
|
104
|
+
forgeloreDir: string;
|
|
105
|
+
config: ForgeloreConfig;
|
|
106
|
+
changes: Change[];
|
|
107
|
+
knowledge: KnowledgeBase;
|
|
108
|
+
globalKnowledge?: KnowledgeBase;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* forgelore configuration management
|
|
113
|
+
* Handles reading/writing forgelore.json config files
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
declare function fileExists(path: string): Promise<boolean>;
|
|
117
|
+
declare function getForgeloreDir(projectRoot: string): string;
|
|
118
|
+
declare function getConfigPath(projectRoot: string): string;
|
|
119
|
+
declare function readConfig(projectRoot: string): Promise<ForgeloreConfig>;
|
|
120
|
+
declare function writeConfig(projectRoot: string, config: ForgeloreConfig): Promise<void>;
|
|
121
|
+
declare function createDefaultConfig(mode: SpecMode): ForgeloreConfig;
|
|
122
|
+
declare function configExists(projectRoot: string): Promise<boolean>;
|
|
123
|
+
declare function getConfigValue(projectRoot: string, key: string): Promise<unknown>;
|
|
124
|
+
declare function setConfigValue(projectRoot: string, key: string, value: unknown): Promise<void>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Spec CRUD operations
|
|
128
|
+
* Create, read, update, list, and archive spec changes
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
declare function getChangesDir(projectRoot: string): string;
|
|
132
|
+
declare function getArchiveDir(projectRoot: string): string;
|
|
133
|
+
declare function getChangePath(projectRoot: string, changeName: string): string;
|
|
134
|
+
declare function scaffoldSpecDirs(projectRoot: string): Promise<void>;
|
|
135
|
+
declare function createChange(projectRoot: string, name: string, proposalContent: string): Promise<Change>;
|
|
136
|
+
declare function readChange(projectRoot: string, name: string): Promise<Change>;
|
|
137
|
+
declare function readChangeFile(projectRoot: string, changeName: string, fileName: string): Promise<string>;
|
|
138
|
+
declare function listChanges(projectRoot: string, includeArchived?: boolean): Promise<Change[]>;
|
|
139
|
+
declare function updateChangeStatus(projectRoot: string, name: string, status: ChangeStatus): Promise<Change>;
|
|
140
|
+
declare function updateTaskStatus(projectRoot: string, changeName: string, taskId: string, status: TaskStatus): Promise<void>;
|
|
141
|
+
declare function archiveChange(projectRoot: string, name: string): Promise<string>;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Knowledge base management
|
|
145
|
+
* Capabilities, decisions, architecture docs, patterns
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
declare function getKnowledgeDir(projectRoot: string): string;
|
|
149
|
+
declare function getCapabilitiesDir(projectRoot: string): string;
|
|
150
|
+
declare function getDecisionsDir(projectRoot: string): string;
|
|
151
|
+
declare function readKnowledge(projectRoot: string): Promise<KnowledgeBase>;
|
|
152
|
+
declare function listCapabilities(projectRoot: string): Promise<Capability[]>;
|
|
153
|
+
declare function addCapability(projectRoot: string, capability: Capability): Promise<void>;
|
|
154
|
+
declare function getCapability(projectRoot: string, id: string): Promise<Capability | null>;
|
|
155
|
+
declare function listDecisions(projectRoot: string): Promise<Decision[]>;
|
|
156
|
+
declare function addDecision(projectRoot: string, decision: Decision): Promise<void>;
|
|
157
|
+
declare function scaffoldKnowledge(projectRoot: string): Promise<void>;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Capability extraction
|
|
161
|
+
* Extract capabilities from archived changes for the knowledge base
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generate a URL-safe slug from a string
|
|
166
|
+
*/
|
|
167
|
+
declare function slugify(text: string): string;
|
|
168
|
+
/**
|
|
169
|
+
* Create a capability record from extracted data
|
|
170
|
+
*/
|
|
171
|
+
declare function createCapability(name: string, description: string, sourceChange: string, files?: string[], tags?: string[]): Capability;
|
|
172
|
+
/**
|
|
173
|
+
* Register a capability in the project's knowledge base
|
|
174
|
+
*/
|
|
175
|
+
declare function registerCapability(projectRoot: string, capability: Capability): Promise<void>;
|
|
176
|
+
/**
|
|
177
|
+
* Read the outcome.md from an archived change (if it exists)
|
|
178
|
+
*/
|
|
179
|
+
declare function readOutcome(archivePath: string): Promise<string | null>;
|
|
180
|
+
/**
|
|
181
|
+
* Write outcome.md to a change directory (step 1 of archive)
|
|
182
|
+
*/
|
|
183
|
+
declare function writeOutcome(changePath: string, content: string): Promise<void>;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Progress tracking
|
|
187
|
+
* Calculate completion percentages and status summaries
|
|
188
|
+
*/
|
|
189
|
+
|
|
190
|
+
interface TaskSummary {
|
|
191
|
+
total: number;
|
|
192
|
+
pending: number;
|
|
193
|
+
inProgress: number;
|
|
194
|
+
passed: number;
|
|
195
|
+
failed: number;
|
|
196
|
+
blocked: number;
|
|
197
|
+
completionPercent: number;
|
|
198
|
+
}
|
|
199
|
+
interface ProjectSummary {
|
|
200
|
+
activeChanges: number;
|
|
201
|
+
archivedChanges: number;
|
|
202
|
+
totalCapabilities: number;
|
|
203
|
+
taskSummary: TaskSummary;
|
|
204
|
+
changes: Array<{
|
|
205
|
+
name: string;
|
|
206
|
+
status: string;
|
|
207
|
+
taskSummary: TaskSummary;
|
|
208
|
+
}>;
|
|
209
|
+
}
|
|
210
|
+
declare function summarizeTasks(tasks: Task[]): TaskSummary;
|
|
211
|
+
declare function getProjectSummary(projectRoot: string): Promise<ProjectSummary>;
|
|
212
|
+
|
|
213
|
+
export { type Capability, type Change, type ChangeStatus, type Decision, type DriftItem, type DriftReport, type DriftSeverity, type EnforcementConfig, type ForgeloreConfig, type ForgeloreProject, type GlobalSpecConfig, type KnowledgeBase, type OrchestrationConfig, type ProjectSummary, type RequirementValidation, type SpecMode, type Task, type TaskStatus, type TaskSummary, type ValidationResult, type ValidationVerdict, addCapability, addDecision, archiveChange, configExists, createCapability, createChange, createDefaultConfig, fileExists, getArchiveDir, getCapabilitiesDir, getCapability, getChangePath, getChangesDir, getConfigPath, getConfigValue, getDecisionsDir, getForgeloreDir, getKnowledgeDir, getProjectSummary, listCapabilities, listChanges, listDecisions, readChange, readChangeFile, readConfig, readKnowledge, readOutcome, registerCapability, scaffoldKnowledge, scaffoldSpecDirs, setConfigValue, slugify, summarizeTasks, updateChangeStatus, updateTaskStatus, writeConfig, writeOutcome };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
// src/config/index.ts
|
|
2
|
+
import { readFile, writeFile, access } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
var CONFIG_FILENAME = "forgelore.json";
|
|
5
|
+
var FORGELORE_DIR = "forgelore";
|
|
6
|
+
var DEFAULT_CONFIG = {
|
|
7
|
+
$schema: "https://forgelore.dev/config.json",
|
|
8
|
+
version: "0.1.0",
|
|
9
|
+
mode: "local",
|
|
10
|
+
orchestration: {
|
|
11
|
+
defaultMode: "sequential",
|
|
12
|
+
maxRetries: 3,
|
|
13
|
+
parallelTracks: 3
|
|
14
|
+
},
|
|
15
|
+
enforcement: {
|
|
16
|
+
requireSpecForChanges: true,
|
|
17
|
+
warnOnUnspeccedEdits: true,
|
|
18
|
+
blockArchiveOnDrift: true,
|
|
19
|
+
autoInjectContext: true
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
async function fileExists(path) {
|
|
23
|
+
try {
|
|
24
|
+
await access(path);
|
|
25
|
+
return true;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function getForgeloreDir(projectRoot) {
|
|
31
|
+
return join(projectRoot, FORGELORE_DIR);
|
|
32
|
+
}
|
|
33
|
+
function getConfigPath(projectRoot) {
|
|
34
|
+
return join(getForgeloreDir(projectRoot), CONFIG_FILENAME);
|
|
35
|
+
}
|
|
36
|
+
async function readConfig(projectRoot) {
|
|
37
|
+
const configPath = getConfigPath(projectRoot);
|
|
38
|
+
if (!await fileExists(configPath)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`No forgelore config found at ${configPath}. Run 'forgelore init' to initialize.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const raw = await readFile(configPath, "utf-8");
|
|
44
|
+
return JSON.parse(raw);
|
|
45
|
+
}
|
|
46
|
+
async function writeConfig(projectRoot, config) {
|
|
47
|
+
const configPath = getConfigPath(projectRoot);
|
|
48
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
49
|
+
}
|
|
50
|
+
function createDefaultConfig(mode) {
|
|
51
|
+
return { ...DEFAULT_CONFIG, mode };
|
|
52
|
+
}
|
|
53
|
+
async function configExists(projectRoot) {
|
|
54
|
+
return fileExists(getConfigPath(projectRoot));
|
|
55
|
+
}
|
|
56
|
+
async function getConfigValue(projectRoot, key) {
|
|
57
|
+
const config = await readConfig(projectRoot);
|
|
58
|
+
const keys = key.split(".");
|
|
59
|
+
let current = config;
|
|
60
|
+
for (const k of keys) {
|
|
61
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
current = current[k];
|
|
65
|
+
}
|
|
66
|
+
return current;
|
|
67
|
+
}
|
|
68
|
+
async function setConfigValue(projectRoot, key, value) {
|
|
69
|
+
const config = await readConfig(projectRoot);
|
|
70
|
+
const keys = key.split(".");
|
|
71
|
+
let current = config;
|
|
72
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
73
|
+
const k = keys[i];
|
|
74
|
+
if (!(k in current) || typeof current[k] !== "object") {
|
|
75
|
+
current[k] = {};
|
|
76
|
+
}
|
|
77
|
+
current = current[k];
|
|
78
|
+
}
|
|
79
|
+
current[keys[keys.length - 1]] = value;
|
|
80
|
+
await writeConfig(projectRoot, config);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/spec/index.ts
|
|
84
|
+
import { readFile as readFile2, writeFile as writeFile2, readdir, mkdir, rename } from "fs/promises";
|
|
85
|
+
import { join as join2 } from "path";
|
|
86
|
+
function getChangesDir(projectRoot) {
|
|
87
|
+
return join2(getForgeloreDir(projectRoot), "changes");
|
|
88
|
+
}
|
|
89
|
+
function getArchiveDir(projectRoot) {
|
|
90
|
+
return join2(getChangesDir(projectRoot), "archive");
|
|
91
|
+
}
|
|
92
|
+
function getChangePath(projectRoot, changeName) {
|
|
93
|
+
return join2(getChangesDir(projectRoot), changeName);
|
|
94
|
+
}
|
|
95
|
+
async function scaffoldSpecDirs(projectRoot) {
|
|
96
|
+
const forgeloreDir = getForgeloreDir(projectRoot);
|
|
97
|
+
await mkdir(join2(forgeloreDir, "changes"), { recursive: true });
|
|
98
|
+
await mkdir(join2(forgeloreDir, "changes", "archive"), { recursive: true });
|
|
99
|
+
await mkdir(join2(forgeloreDir, "knowledge", "capabilities"), { recursive: true });
|
|
100
|
+
await mkdir(join2(forgeloreDir, "knowledge", "decisions"), { recursive: true });
|
|
101
|
+
}
|
|
102
|
+
async function createChange(projectRoot, name, proposalContent) {
|
|
103
|
+
const changePath = getChangePath(projectRoot, name);
|
|
104
|
+
if (await fileExists(changePath)) {
|
|
105
|
+
throw new Error(`Change '${name}' already exists at ${changePath}`);
|
|
106
|
+
}
|
|
107
|
+
await mkdir(changePath, { recursive: true });
|
|
108
|
+
await mkdir(join2(changePath, "specs"), { recursive: true });
|
|
109
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
110
|
+
await writeFile2(join2(changePath, "proposal.md"), proposalContent, "utf-8");
|
|
111
|
+
await writeFile2(
|
|
112
|
+
join2(changePath, "specs", "requirements.md"),
|
|
113
|
+
`# Requirements: ${name}
|
|
114
|
+
|
|
115
|
+
<!-- Define the requirements for this change -->
|
|
116
|
+
|
|
117
|
+
## Functional Requirements
|
|
118
|
+
|
|
119
|
+
1.
|
|
120
|
+
|
|
121
|
+
## Non-Functional Requirements
|
|
122
|
+
|
|
123
|
+
1.
|
|
124
|
+
`,
|
|
125
|
+
"utf-8"
|
|
126
|
+
);
|
|
127
|
+
await writeFile2(
|
|
128
|
+
join2(changePath, "specs", "scenarios.md"),
|
|
129
|
+
`# Scenarios: ${name}
|
|
130
|
+
|
|
131
|
+
<!-- Define acceptance scenarios -->
|
|
132
|
+
|
|
133
|
+
## Happy Path
|
|
134
|
+
|
|
135
|
+
1.
|
|
136
|
+
|
|
137
|
+
## Edge Cases
|
|
138
|
+
|
|
139
|
+
1.
|
|
140
|
+
|
|
141
|
+
## Error Cases
|
|
142
|
+
|
|
143
|
+
1.
|
|
144
|
+
`,
|
|
145
|
+
"utf-8"
|
|
146
|
+
);
|
|
147
|
+
await writeFile2(
|
|
148
|
+
join2(changePath, "design.md"),
|
|
149
|
+
`# Design: ${name}
|
|
150
|
+
|
|
151
|
+
<!-- Technical approach and architecture decisions -->
|
|
152
|
+
|
|
153
|
+
## Approach
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
## Key Decisions
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
## Files to Modify
|
|
160
|
+
|
|
161
|
+
-
|
|
162
|
+
|
|
163
|
+
## Dependencies
|
|
164
|
+
|
|
165
|
+
-
|
|
166
|
+
`,
|
|
167
|
+
"utf-8"
|
|
168
|
+
);
|
|
169
|
+
await writeFile2(
|
|
170
|
+
join2(changePath, "tasks.md"),
|
|
171
|
+
`# Tasks: ${name}
|
|
172
|
+
|
|
173
|
+
<!-- Implementation checklist -->
|
|
174
|
+
|
|
175
|
+
| ID | Task | Status | Category | Notes |
|
|
176
|
+
|----|------|--------|----------|-------|
|
|
177
|
+
| 1 | | pending | | |
|
|
178
|
+
`,
|
|
179
|
+
"utf-8"
|
|
180
|
+
);
|
|
181
|
+
const change = {
|
|
182
|
+
name,
|
|
183
|
+
status: "proposed",
|
|
184
|
+
createdAt: now,
|
|
185
|
+
updatedAt: now,
|
|
186
|
+
path: changePath,
|
|
187
|
+
tasks: []
|
|
188
|
+
};
|
|
189
|
+
await writeFile2(
|
|
190
|
+
join2(changePath, ".forge-meta.json"),
|
|
191
|
+
JSON.stringify(change, null, 2) + "\n",
|
|
192
|
+
"utf-8"
|
|
193
|
+
);
|
|
194
|
+
return change;
|
|
195
|
+
}
|
|
196
|
+
async function readChange(projectRoot, name) {
|
|
197
|
+
const changePath = getChangePath(projectRoot, name);
|
|
198
|
+
const metaPath = join2(changePath, ".forge-meta.json");
|
|
199
|
+
if (!await fileExists(metaPath)) {
|
|
200
|
+
throw new Error(`Change '${name}' not found at ${changePath}`);
|
|
201
|
+
}
|
|
202
|
+
const raw = await readFile2(metaPath, "utf-8");
|
|
203
|
+
return JSON.parse(raw);
|
|
204
|
+
}
|
|
205
|
+
async function readChangeFile(projectRoot, changeName, fileName) {
|
|
206
|
+
const filePath = join2(getChangePath(projectRoot, changeName), fileName);
|
|
207
|
+
if (!await fileExists(filePath)) {
|
|
208
|
+
throw new Error(`File '${fileName}' not found in change '${changeName}'`);
|
|
209
|
+
}
|
|
210
|
+
return readFile2(filePath, "utf-8");
|
|
211
|
+
}
|
|
212
|
+
async function listChanges(projectRoot, includeArchived = false) {
|
|
213
|
+
const changesDir = getChangesDir(projectRoot);
|
|
214
|
+
if (!await fileExists(changesDir)) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
const entries = await readdir(changesDir, { withFileTypes: true });
|
|
218
|
+
const changes = [];
|
|
219
|
+
for (const entry of entries) {
|
|
220
|
+
if (!entry.isDirectory() || entry.name === "archive") continue;
|
|
221
|
+
const metaPath = join2(changesDir, entry.name, ".forge-meta.json");
|
|
222
|
+
if (await fileExists(metaPath)) {
|
|
223
|
+
const raw = await readFile2(metaPath, "utf-8");
|
|
224
|
+
changes.push(JSON.parse(raw));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (includeArchived) {
|
|
228
|
+
const archiveDir = getArchiveDir(projectRoot);
|
|
229
|
+
if (await fileExists(archiveDir)) {
|
|
230
|
+
const archiveEntries = await readdir(archiveDir, { withFileTypes: true });
|
|
231
|
+
for (const entry of archiveEntries) {
|
|
232
|
+
if (!entry.isDirectory()) continue;
|
|
233
|
+
const metaPath = join2(archiveDir, entry.name, ".forge-meta.json");
|
|
234
|
+
if (await fileExists(metaPath)) {
|
|
235
|
+
const raw = await readFile2(metaPath, "utf-8");
|
|
236
|
+
changes.push(JSON.parse(raw));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return changes.sort(
|
|
242
|
+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
async function updateChangeStatus(projectRoot, name, status) {
|
|
246
|
+
const change = await readChange(projectRoot, name);
|
|
247
|
+
change.status = status;
|
|
248
|
+
change.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
249
|
+
const metaPath = join2(change.path, ".forge-meta.json");
|
|
250
|
+
await writeFile2(metaPath, JSON.stringify(change, null, 2) + "\n", "utf-8");
|
|
251
|
+
return change;
|
|
252
|
+
}
|
|
253
|
+
async function updateTaskStatus(projectRoot, changeName, taskId, status) {
|
|
254
|
+
const change = await readChange(projectRoot, changeName);
|
|
255
|
+
const task = change.tasks.find((t) => t.id === taskId);
|
|
256
|
+
if (task) {
|
|
257
|
+
task.status = status;
|
|
258
|
+
}
|
|
259
|
+
change.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
260
|
+
const metaPath = join2(change.path, ".forge-meta.json");
|
|
261
|
+
await writeFile2(metaPath, JSON.stringify(change, null, 2) + "\n", "utf-8");
|
|
262
|
+
}
|
|
263
|
+
async function archiveChange(projectRoot, name) {
|
|
264
|
+
const changePath = getChangePath(projectRoot, name);
|
|
265
|
+
if (!await fileExists(changePath)) {
|
|
266
|
+
throw new Error(`Change '${name}' not found`);
|
|
267
|
+
}
|
|
268
|
+
const archiveDir = getArchiveDir(projectRoot);
|
|
269
|
+
const datestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
270
|
+
const archiveName = `${datestamp}-${name}`;
|
|
271
|
+
const archivePath = join2(archiveDir, archiveName);
|
|
272
|
+
await updateChangeStatus(projectRoot, name, "archived");
|
|
273
|
+
await rename(changePath, archivePath);
|
|
274
|
+
return archivePath;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/knowledge/index.ts
|
|
278
|
+
import { readFile as readFile3, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2 } from "fs/promises";
|
|
279
|
+
import { join as join3 } from "path";
|
|
280
|
+
function getKnowledgeDir(projectRoot) {
|
|
281
|
+
return join3(getForgeloreDir(projectRoot), "knowledge");
|
|
282
|
+
}
|
|
283
|
+
function getCapabilitiesDir(projectRoot) {
|
|
284
|
+
return join3(getKnowledgeDir(projectRoot), "capabilities");
|
|
285
|
+
}
|
|
286
|
+
function getDecisionsDir(projectRoot) {
|
|
287
|
+
return join3(getKnowledgeDir(projectRoot), "decisions");
|
|
288
|
+
}
|
|
289
|
+
async function readKnowledge(projectRoot) {
|
|
290
|
+
const knowledgeDir = getKnowledgeDir(projectRoot);
|
|
291
|
+
const kb = {
|
|
292
|
+
capabilities: await listCapabilities(projectRoot),
|
|
293
|
+
decisions: await listDecisions(projectRoot)
|
|
294
|
+
};
|
|
295
|
+
const archPath = join3(knowledgeDir, "architecture.md");
|
|
296
|
+
if (await fileExists(archPath)) {
|
|
297
|
+
kb.architecture = await readFile3(archPath, "utf-8");
|
|
298
|
+
}
|
|
299
|
+
const patternsPath = join3(knowledgeDir, "patterns.md");
|
|
300
|
+
if (await fileExists(patternsPath)) {
|
|
301
|
+
kb.patterns = await readFile3(patternsPath, "utf-8");
|
|
302
|
+
}
|
|
303
|
+
const glossaryPath = join3(knowledgeDir, "glossary.md");
|
|
304
|
+
if (await fileExists(glossaryPath)) {
|
|
305
|
+
kb.glossary = await readFile3(glossaryPath, "utf-8");
|
|
306
|
+
}
|
|
307
|
+
return kb;
|
|
308
|
+
}
|
|
309
|
+
async function listCapabilities(projectRoot) {
|
|
310
|
+
const dir = getCapabilitiesDir(projectRoot);
|
|
311
|
+
if (!await fileExists(dir)) return [];
|
|
312
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
313
|
+
const capabilities = [];
|
|
314
|
+
for (const entry of entries) {
|
|
315
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
316
|
+
const raw = await readFile3(join3(dir, entry.name), "utf-8");
|
|
317
|
+
capabilities.push(JSON.parse(raw));
|
|
318
|
+
}
|
|
319
|
+
return capabilities.sort(
|
|
320
|
+
(a, b) => new Date(b.archivedAt).getTime() - new Date(a.archivedAt).getTime()
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
async function addCapability(projectRoot, capability) {
|
|
324
|
+
const dir = getCapabilitiesDir(projectRoot);
|
|
325
|
+
await mkdir2(dir, { recursive: true });
|
|
326
|
+
const fileName = `${capability.id}.json`;
|
|
327
|
+
await writeFile3(
|
|
328
|
+
join3(dir, fileName),
|
|
329
|
+
JSON.stringify(capability, null, 2) + "\n",
|
|
330
|
+
"utf-8"
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
async function getCapability(projectRoot, id) {
|
|
334
|
+
const filePath = join3(getCapabilitiesDir(projectRoot), `${id}.json`);
|
|
335
|
+
if (!await fileExists(filePath)) return null;
|
|
336
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
337
|
+
return JSON.parse(raw);
|
|
338
|
+
}
|
|
339
|
+
async function listDecisions(projectRoot) {
|
|
340
|
+
const dir = getDecisionsDir(projectRoot);
|
|
341
|
+
if (!await fileExists(dir)) return [];
|
|
342
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
343
|
+
const decisions = [];
|
|
344
|
+
for (const entry of entries) {
|
|
345
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
346
|
+
const raw = await readFile3(join3(dir, entry.name), "utf-8");
|
|
347
|
+
decisions.push(JSON.parse(raw));
|
|
348
|
+
}
|
|
349
|
+
return decisions.sort((a, b) => a.id.localeCompare(b.id));
|
|
350
|
+
}
|
|
351
|
+
async function addDecision(projectRoot, decision) {
|
|
352
|
+
const dir = getDecisionsDir(projectRoot);
|
|
353
|
+
await mkdir2(dir, { recursive: true });
|
|
354
|
+
const fileName = `${decision.id}.json`;
|
|
355
|
+
await writeFile3(
|
|
356
|
+
join3(dir, fileName),
|
|
357
|
+
JSON.stringify(decision, null, 2) + "\n",
|
|
358
|
+
"utf-8"
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
async function scaffoldKnowledge(projectRoot) {
|
|
362
|
+
const knowledgeDir = getKnowledgeDir(projectRoot);
|
|
363
|
+
await mkdir2(join3(knowledgeDir, "capabilities"), { recursive: true });
|
|
364
|
+
await mkdir2(join3(knowledgeDir, "decisions"), { recursive: true });
|
|
365
|
+
const archPath = join3(knowledgeDir, "architecture.md");
|
|
366
|
+
if (!await fileExists(archPath)) {
|
|
367
|
+
await writeFile3(
|
|
368
|
+
archPath,
|
|
369
|
+
`# Architecture
|
|
370
|
+
|
|
371
|
+
<!-- Living architecture documentation. Updated as the system evolves. -->
|
|
372
|
+
|
|
373
|
+
## Overview
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
## Components
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
## Data Flow
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
## Key Integrations
|
|
383
|
+
|
|
384
|
+
`,
|
|
385
|
+
"utf-8"
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
const patternsPath = join3(knowledgeDir, "patterns.md");
|
|
389
|
+
if (!await fileExists(patternsPath)) {
|
|
390
|
+
await writeFile3(
|
|
391
|
+
patternsPath,
|
|
392
|
+
`# Patterns
|
|
393
|
+
|
|
394
|
+
<!-- Established patterns in this codebase. Agents should follow these. -->
|
|
395
|
+
|
|
396
|
+
## Code Patterns
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
## Naming Conventions
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
## Error Handling
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
## Testing Patterns
|
|
406
|
+
|
|
407
|
+
`,
|
|
408
|
+
"utf-8"
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
const glossaryPath = join3(knowledgeDir, "glossary.md");
|
|
412
|
+
if (!await fileExists(glossaryPath)) {
|
|
413
|
+
await writeFile3(
|
|
414
|
+
glossaryPath,
|
|
415
|
+
`# Glossary
|
|
416
|
+
|
|
417
|
+
<!-- Domain-specific terms used in this project -->
|
|
418
|
+
|
|
419
|
+
| Term | Definition |
|
|
420
|
+
|------|------------|
|
|
421
|
+
| | |
|
|
422
|
+
`,
|
|
423
|
+
"utf-8"
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// src/capabilities/index.ts
|
|
429
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
430
|
+
import { join as join4 } from "path";
|
|
431
|
+
function slugify(text) {
|
|
432
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
433
|
+
}
|
|
434
|
+
function createCapability(name, description, sourceChange, files = [], tags = []) {
|
|
435
|
+
return {
|
|
436
|
+
id: slugify(name),
|
|
437
|
+
name,
|
|
438
|
+
description,
|
|
439
|
+
sourceChange,
|
|
440
|
+
archivedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
441
|
+
files,
|
|
442
|
+
tags
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
async function registerCapability(projectRoot, capability) {
|
|
446
|
+
await addCapability(projectRoot, capability);
|
|
447
|
+
}
|
|
448
|
+
async function readOutcome(archivePath) {
|
|
449
|
+
const outcomePath = join4(archivePath, "outcome.md");
|
|
450
|
+
if (!await fileExists(outcomePath)) return null;
|
|
451
|
+
return readFile4(outcomePath, "utf-8");
|
|
452
|
+
}
|
|
453
|
+
async function writeOutcome(changePath, content) {
|
|
454
|
+
const { writeFile: wf } = await import("fs/promises");
|
|
455
|
+
await wf(join4(changePath, "outcome.md"), content, "utf-8");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/progress/index.ts
|
|
459
|
+
function summarizeTasks(tasks) {
|
|
460
|
+
const total = tasks.length;
|
|
461
|
+
if (total === 0) {
|
|
462
|
+
return {
|
|
463
|
+
total: 0,
|
|
464
|
+
pending: 0,
|
|
465
|
+
inProgress: 0,
|
|
466
|
+
passed: 0,
|
|
467
|
+
failed: 0,
|
|
468
|
+
blocked: 0,
|
|
469
|
+
completionPercent: 0
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
const pending = tasks.filter((t) => t.status === "pending").length;
|
|
473
|
+
const inProgress = tasks.filter(
|
|
474
|
+
(t) => t.status === "claimed" || t.status === "in-progress" || t.status === "implemented" || t.status === "validating"
|
|
475
|
+
).length;
|
|
476
|
+
const passed = tasks.filter((t) => t.status === "passed").length;
|
|
477
|
+
const failed = tasks.filter((t) => t.status === "failed").length;
|
|
478
|
+
const blocked = tasks.filter((t) => t.status === "blocked").length;
|
|
479
|
+
const completionPercent = Math.round(passed / total * 100);
|
|
480
|
+
return { total, pending, inProgress, passed, failed, blocked, completionPercent };
|
|
481
|
+
}
|
|
482
|
+
async function getProjectSummary(projectRoot) {
|
|
483
|
+
const activeChanges = await listChanges(projectRoot, false);
|
|
484
|
+
const allChanges = await listChanges(projectRoot, true);
|
|
485
|
+
const archivedChanges = allChanges.filter((c) => c.status === "archived");
|
|
486
|
+
const capabilities = await listCapabilities(projectRoot);
|
|
487
|
+
const allTasks = activeChanges.flatMap((c) => c.tasks);
|
|
488
|
+
return {
|
|
489
|
+
activeChanges: activeChanges.length,
|
|
490
|
+
archivedChanges: archivedChanges.length,
|
|
491
|
+
totalCapabilities: capabilities.length,
|
|
492
|
+
taskSummary: summarizeTasks(allTasks),
|
|
493
|
+
changes: activeChanges.map((c) => ({
|
|
494
|
+
name: c.name,
|
|
495
|
+
status: c.status,
|
|
496
|
+
taskSummary: summarizeTasks(c.tasks)
|
|
497
|
+
}))
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
export {
|
|
501
|
+
addCapability,
|
|
502
|
+
addDecision,
|
|
503
|
+
archiveChange,
|
|
504
|
+
configExists,
|
|
505
|
+
createCapability,
|
|
506
|
+
createChange,
|
|
507
|
+
createDefaultConfig,
|
|
508
|
+
fileExists,
|
|
509
|
+
getArchiveDir,
|
|
510
|
+
getCapabilitiesDir,
|
|
511
|
+
getCapability,
|
|
512
|
+
getChangePath,
|
|
513
|
+
getChangesDir,
|
|
514
|
+
getConfigPath,
|
|
515
|
+
getConfigValue,
|
|
516
|
+
getDecisionsDir,
|
|
517
|
+
getForgeloreDir,
|
|
518
|
+
getKnowledgeDir,
|
|
519
|
+
getProjectSummary,
|
|
520
|
+
listCapabilities,
|
|
521
|
+
listChanges,
|
|
522
|
+
listDecisions,
|
|
523
|
+
readChange,
|
|
524
|
+
readChangeFile,
|
|
525
|
+
readConfig,
|
|
526
|
+
readKnowledge,
|
|
527
|
+
readOutcome,
|
|
528
|
+
registerCapability,
|
|
529
|
+
scaffoldKnowledge,
|
|
530
|
+
scaffoldSpecDirs,
|
|
531
|
+
setConfigValue,
|
|
532
|
+
slugify,
|
|
533
|
+
summarizeTasks,
|
|
534
|
+
updateChangeStatus,
|
|
535
|
+
updateTaskStatus,
|
|
536
|
+
writeConfig,
|
|
537
|
+
writeOutcome
|
|
538
|
+
};
|
|
539
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/index.ts","../src/spec/index.ts","../src/knowledge/index.ts","../src/capabilities/index.ts","../src/progress/index.ts"],"sourcesContent":["/**\n * forgelore configuration management\n * Handles reading/writing forgelore.json config files\n */\n\nimport { readFile, writeFile, access } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { ForgeloreConfig, SpecMode } from \"../types/index.js\";\n\nconst CONFIG_FILENAME = \"forgelore.json\";\nconst FORGELORE_DIR = \"forgelore\";\n\nconst DEFAULT_CONFIG: ForgeloreConfig = {\n $schema: \"https://forgelore.dev/config.json\",\n version: \"0.1.0\",\n mode: \"local\",\n orchestration: {\n defaultMode: \"sequential\",\n maxRetries: 3,\n parallelTracks: 3,\n },\n enforcement: {\n requireSpecForChanges: true,\n warnOnUnspeccedEdits: true,\n blockArchiveOnDrift: true,\n autoInjectContext: true,\n },\n};\n\nexport async function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getForgeloreDir(projectRoot: string): string {\n return join(projectRoot, FORGELORE_DIR);\n}\n\nexport function getConfigPath(projectRoot: string): string {\n return join(getForgeloreDir(projectRoot), CONFIG_FILENAME);\n}\n\nexport async function readConfig(projectRoot: string): Promise<ForgeloreConfig> {\n const configPath = getConfigPath(projectRoot);\n if (!(await fileExists(configPath))) {\n throw new Error(\n `No forgelore config found at ${configPath}. Run 'forgelore init' to initialize.`\n );\n }\n\n const raw = await readFile(configPath, \"utf-8\");\n return JSON.parse(raw) as ForgeloreConfig;\n}\n\nexport async function writeConfig(\n projectRoot: string,\n config: ForgeloreConfig\n): Promise<void> {\n const configPath = getConfigPath(projectRoot);\n await writeFile(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\nexport function createDefaultConfig(mode: SpecMode): ForgeloreConfig {\n return { ...DEFAULT_CONFIG, mode };\n}\n\nexport async function configExists(projectRoot: string): Promise<boolean> {\n return fileExists(getConfigPath(projectRoot));\n}\n\nexport async function getConfigValue(\n projectRoot: string,\n key: string\n): Promise<unknown> {\n const config = await readConfig(projectRoot);\n const keys = key.split(\".\");\n let current: unknown = config;\n for (const k of keys) {\n if (current === null || current === undefined || typeof current !== \"object\") {\n return undefined;\n }\n current = (current as Record<string, unknown>)[k];\n }\n return current;\n}\n\nexport async function setConfigValue(\n projectRoot: string,\n key: string,\n value: unknown\n): Promise<void> {\n const config = await readConfig(projectRoot);\n const keys = key.split(\".\");\n let current: Record<string, unknown> = config as unknown as Record<string, unknown>;\n for (let i = 0; i < keys.length - 1; i++) {\n const k = keys[i];\n if (!(k in current) || typeof current[k] !== \"object\") {\n current[k] = {};\n }\n current = current[k] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = value;\n await writeConfig(projectRoot, config);\n}\n","/**\n * Spec CRUD operations\n * Create, read, update, list, and archive spec changes\n */\n\nimport { readFile, writeFile, readdir, mkdir, rename, rm } from \"node:fs/promises\";\nimport { join, basename } from \"node:path\";\nimport { fileExists, getForgeloreDir } from \"../config/index.js\";\nimport type { Change, ChangeStatus, Task, TaskStatus } from \"../types/index.js\";\n\n// --- Paths ---\n\nexport function getChangesDir(projectRoot: string): string {\n return join(getForgeloreDir(projectRoot), \"changes\");\n}\n\nexport function getArchiveDir(projectRoot: string): string {\n return join(getChangesDir(projectRoot), \"archive\");\n}\n\nexport function getChangePath(projectRoot: string, changeName: string): string {\n return join(getChangesDir(projectRoot), changeName);\n}\n\n// --- Scaffold ---\n\nexport async function scaffoldSpecDirs(projectRoot: string): Promise<void> {\n const forgeloreDir = getForgeloreDir(projectRoot);\n await mkdir(join(forgeloreDir, \"changes\"), { recursive: true });\n await mkdir(join(forgeloreDir, \"changes\", \"archive\"), { recursive: true });\n await mkdir(join(forgeloreDir, \"knowledge\", \"capabilities\"), { recursive: true });\n await mkdir(join(forgeloreDir, \"knowledge\", \"decisions\"), { recursive: true });\n}\n\n// --- Create ---\n\nexport async function createChange(\n projectRoot: string,\n name: string,\n proposalContent: string\n): Promise<Change> {\n const changePath = getChangePath(projectRoot, name);\n\n if (await fileExists(changePath)) {\n throw new Error(`Change '${name}' already exists at ${changePath}`);\n }\n\n await mkdir(changePath, { recursive: true });\n await mkdir(join(changePath, \"specs\"), { recursive: true });\n\n const now = new Date().toISOString();\n\n // Write proposal\n await writeFile(join(changePath, \"proposal.md\"), proposalContent, \"utf-8\");\n\n // Write empty spec files from templates\n await writeFile(\n join(changePath, \"specs\", \"requirements.md\"),\n `# Requirements: ${name}\\n\\n<!-- Define the requirements for this change -->\\n\\n## Functional Requirements\\n\\n1. \\n\\n## Non-Functional Requirements\\n\\n1. \\n`,\n \"utf-8\"\n );\n\n await writeFile(\n join(changePath, \"specs\", \"scenarios.md\"),\n `# Scenarios: ${name}\\n\\n<!-- Define acceptance scenarios -->\\n\\n## Happy Path\\n\\n1. \\n\\n## Edge Cases\\n\\n1. \\n\\n## Error Cases\\n\\n1. \\n`,\n \"utf-8\"\n );\n\n await writeFile(\n join(changePath, \"design.md\"),\n `# Design: ${name}\\n\\n<!-- Technical approach and architecture decisions -->\\n\\n## Approach\\n\\n\\n## Key Decisions\\n\\n\\n## Files to Modify\\n\\n- \\n\\n## Dependencies\\n\\n- \\n`,\n \"utf-8\"\n );\n\n await writeFile(\n join(changePath, \"tasks.md\"),\n `# Tasks: ${name}\\n\\n<!-- Implementation checklist -->\\n\\n| ID | Task | Status | Category | Notes |\\n|----|------|--------|----------|-------|\\n| 1 | | pending | | |\\n`,\n \"utf-8\"\n );\n\n const change: Change = {\n name,\n status: \"proposed\",\n createdAt: now,\n updatedAt: now,\n path: changePath,\n tasks: [],\n };\n\n // Write metadata\n await writeFile(\n join(changePath, \".forge-meta.json\"),\n JSON.stringify(change, null, 2) + \"\\n\",\n \"utf-8\"\n );\n\n return change;\n}\n\n// --- Read ---\n\nexport async function readChange(\n projectRoot: string,\n name: string\n): Promise<Change> {\n const changePath = getChangePath(projectRoot, name);\n const metaPath = join(changePath, \".forge-meta.json\");\n\n if (!(await fileExists(metaPath))) {\n throw new Error(`Change '${name}' not found at ${changePath}`);\n }\n\n const raw = await readFile(metaPath, \"utf-8\");\n return JSON.parse(raw) as Change;\n}\n\nexport async function readChangeFile(\n projectRoot: string,\n changeName: string,\n fileName: string\n): Promise<string> {\n const filePath = join(getChangePath(projectRoot, changeName), fileName);\n if (!(await fileExists(filePath))) {\n throw new Error(`File '${fileName}' not found in change '${changeName}'`);\n }\n return readFile(filePath, \"utf-8\");\n}\n\n// --- List ---\n\nexport async function listChanges(\n projectRoot: string,\n includeArchived = false\n): Promise<Change[]> {\n const changesDir = getChangesDir(projectRoot);\n\n if (!(await fileExists(changesDir))) {\n return [];\n }\n\n const entries = await readdir(changesDir, { withFileTypes: true });\n const changes: Change[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name === \"archive\") continue;\n\n const metaPath = join(changesDir, entry.name, \".forge-meta.json\");\n if (await fileExists(metaPath)) {\n const raw = await readFile(metaPath, \"utf-8\");\n changes.push(JSON.parse(raw) as Change);\n }\n }\n\n if (includeArchived) {\n const archiveDir = getArchiveDir(projectRoot);\n if (await fileExists(archiveDir)) {\n const archiveEntries = await readdir(archiveDir, { withFileTypes: true });\n for (const entry of archiveEntries) {\n if (!entry.isDirectory()) continue;\n const metaPath = join(archiveDir, entry.name, \".forge-meta.json\");\n if (await fileExists(metaPath)) {\n const raw = await readFile(metaPath, \"utf-8\");\n changes.push(JSON.parse(raw) as Change);\n }\n }\n }\n }\n\n return changes.sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\n );\n}\n\n// --- Update ---\n\nexport async function updateChangeStatus(\n projectRoot: string,\n name: string,\n status: ChangeStatus\n): Promise<Change> {\n const change = await readChange(projectRoot, name);\n change.status = status;\n change.updatedAt = new Date().toISOString();\n\n const metaPath = join(change.path, \".forge-meta.json\");\n await writeFile(metaPath, JSON.stringify(change, null, 2) + \"\\n\", \"utf-8\");\n\n return change;\n}\n\nexport async function updateTaskStatus(\n projectRoot: string,\n changeName: string,\n taskId: string,\n status: TaskStatus\n): Promise<void> {\n const change = await readChange(projectRoot, changeName);\n const task = change.tasks.find((t) => t.id === taskId);\n if (task) {\n task.status = status;\n }\n change.updatedAt = new Date().toISOString();\n\n const metaPath = join(change.path, \".forge-meta.json\");\n await writeFile(metaPath, JSON.stringify(change, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// --- Archive ---\n\nexport async function archiveChange(\n projectRoot: string,\n name: string\n): Promise<string> {\n const changePath = getChangePath(projectRoot, name);\n if (!(await fileExists(changePath))) {\n throw new Error(`Change '${name}' not found`);\n }\n\n const archiveDir = getArchiveDir(projectRoot);\n const datestamp = new Date().toISOString().slice(0, 10);\n const archiveName = `${datestamp}-${name}`;\n const archivePath = join(archiveDir, archiveName);\n\n // Update status before archiving\n await updateChangeStatus(projectRoot, name, \"archived\");\n\n // Move to archive\n await rename(changePath, archivePath);\n\n return archivePath;\n}\n","/**\n * Knowledge base management\n * Capabilities, decisions, architecture docs, patterns\n */\n\nimport { readFile, writeFile, readdir, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { fileExists, getForgeloreDir } from \"../config/index.js\";\nimport type { Capability, Decision, KnowledgeBase } from \"../types/index.js\";\n\n// --- Paths ---\n\nexport function getKnowledgeDir(projectRoot: string): string {\n return join(getForgeloreDir(projectRoot), \"knowledge\");\n}\n\nexport function getCapabilitiesDir(projectRoot: string): string {\n return join(getKnowledgeDir(projectRoot), \"capabilities\");\n}\n\nexport function getDecisionsDir(projectRoot: string): string {\n return join(getKnowledgeDir(projectRoot), \"decisions\");\n}\n\n// --- Read Knowledge Base ---\n\nexport async function readKnowledge(projectRoot: string): Promise<KnowledgeBase> {\n const knowledgeDir = getKnowledgeDir(projectRoot);\n\n const kb: KnowledgeBase = {\n capabilities: await listCapabilities(projectRoot),\n decisions: await listDecisions(projectRoot),\n };\n\n // Read optional markdown files\n const archPath = join(knowledgeDir, \"architecture.md\");\n if (await fileExists(archPath)) {\n kb.architecture = await readFile(archPath, \"utf-8\");\n }\n\n const patternsPath = join(knowledgeDir, \"patterns.md\");\n if (await fileExists(patternsPath)) {\n kb.patterns = await readFile(patternsPath, \"utf-8\");\n }\n\n const glossaryPath = join(knowledgeDir, \"glossary.md\");\n if (await fileExists(glossaryPath)) {\n kb.glossary = await readFile(glossaryPath, \"utf-8\");\n }\n\n return kb;\n}\n\n// --- Capabilities ---\n\nexport async function listCapabilities(projectRoot: string): Promise<Capability[]> {\n const dir = getCapabilitiesDir(projectRoot);\n if (!(await fileExists(dir))) return [];\n\n const entries = await readdir(dir, { withFileTypes: true });\n const capabilities: Capability[] = [];\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue;\n const raw = await readFile(join(dir, entry.name), \"utf-8\");\n capabilities.push(JSON.parse(raw) as Capability);\n }\n\n return capabilities.sort(\n (a, b) => new Date(b.archivedAt).getTime() - new Date(a.archivedAt).getTime()\n );\n}\n\nexport async function addCapability(\n projectRoot: string,\n capability: Capability\n): Promise<void> {\n const dir = getCapabilitiesDir(projectRoot);\n await mkdir(dir, { recursive: true });\n\n const fileName = `${capability.id}.json`;\n await writeFile(\n join(dir, fileName),\n JSON.stringify(capability, null, 2) + \"\\n\",\n \"utf-8\"\n );\n}\n\nexport async function getCapability(\n projectRoot: string,\n id: string\n): Promise<Capability | null> {\n const filePath = join(getCapabilitiesDir(projectRoot), `${id}.json`);\n if (!(await fileExists(filePath))) return null;\n const raw = await readFile(filePath, \"utf-8\");\n return JSON.parse(raw) as Capability;\n}\n\n// --- Decisions ---\n\nexport async function listDecisions(projectRoot: string): Promise<Decision[]> {\n const dir = getDecisionsDir(projectRoot);\n if (!(await fileExists(dir))) return [];\n\n const entries = await readdir(dir, { withFileTypes: true });\n const decisions: Decision[] = [];\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue;\n const raw = await readFile(join(dir, entry.name), \"utf-8\");\n decisions.push(JSON.parse(raw) as Decision);\n }\n\n return decisions.sort((a, b) => a.id.localeCompare(b.id));\n}\n\nexport async function addDecision(\n projectRoot: string,\n decision: Decision\n): Promise<void> {\n const dir = getDecisionsDir(projectRoot);\n await mkdir(dir, { recursive: true });\n\n const fileName = `${decision.id}.json`;\n await writeFile(\n join(dir, fileName),\n JSON.stringify(decision, null, 2) + \"\\n\",\n \"utf-8\"\n );\n}\n\n// --- Scaffold Knowledge ---\n\nexport async function scaffoldKnowledge(projectRoot: string): Promise<void> {\n const knowledgeDir = getKnowledgeDir(projectRoot);\n await mkdir(join(knowledgeDir, \"capabilities\"), { recursive: true });\n await mkdir(join(knowledgeDir, \"decisions\"), { recursive: true });\n\n const archPath = join(knowledgeDir, \"architecture.md\");\n if (!(await fileExists(archPath))) {\n await writeFile(\n archPath,\n `# Architecture\\n\\n<!-- Living architecture documentation. Updated as the system evolves. -->\\n\\n## Overview\\n\\n\\n## Components\\n\\n\\n## Data Flow\\n\\n\\n## Key Integrations\\n\\n`,\n \"utf-8\"\n );\n }\n\n const patternsPath = join(knowledgeDir, \"patterns.md\");\n if (!(await fileExists(patternsPath))) {\n await writeFile(\n patternsPath,\n `# Patterns\\n\\n<!-- Established patterns in this codebase. Agents should follow these. -->\\n\\n## Code Patterns\\n\\n\\n## Naming Conventions\\n\\n\\n## Error Handling\\n\\n\\n## Testing Patterns\\n\\n`,\n \"utf-8\"\n );\n }\n\n const glossaryPath = join(knowledgeDir, \"glossary.md\");\n if (!(await fileExists(glossaryPath))) {\n await writeFile(\n glossaryPath,\n `# Glossary\\n\\n<!-- Domain-specific terms used in this project -->\\n\\n| Term | Definition |\\n|------|------------|\\n| | |\\n`,\n \"utf-8\"\n );\n }\n}\n","/**\n * Capability extraction\n * Extract capabilities from archived changes for the knowledge base\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { fileExists } from \"../config/index.js\";\nimport { addCapability } from \"../knowledge/index.js\";\nimport type { Capability } from \"../types/index.js\";\n\n/**\n * Generate a URL-safe slug from a string\n */\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n\n/**\n * Create a capability record from extracted data\n */\nexport function createCapability(\n name: string,\n description: string,\n sourceChange: string,\n files: string[] = [],\n tags: string[] = []\n): Capability {\n return {\n id: slugify(name),\n name,\n description,\n sourceChange,\n archivedAt: new Date().toISOString(),\n files,\n tags,\n };\n}\n\n/**\n * Register a capability in the project's knowledge base\n */\nexport async function registerCapability(\n projectRoot: string,\n capability: Capability\n): Promise<void> {\n await addCapability(projectRoot, capability);\n}\n\n/**\n * Read the outcome.md from an archived change (if it exists)\n */\nexport async function readOutcome(archivePath: string): Promise<string | null> {\n const outcomePath = join(archivePath, \"outcome.md\");\n if (!(await fileExists(outcomePath))) return null;\n return readFile(outcomePath, \"utf-8\");\n}\n\n/**\n * Write outcome.md to a change directory (step 1 of archive)\n */\nexport async function writeOutcome(\n changePath: string,\n content: string\n): Promise<void> {\n const { writeFile: wf } = await import(\"node:fs/promises\");\n await wf(join(changePath, \"outcome.md\"), content, \"utf-8\");\n}\n","/**\n * Progress tracking\n * Calculate completion percentages and status summaries\n */\n\nimport type { Change, Task, TaskStatus, ForgeloreProject } from \"../types/index.js\";\nimport { listChanges } from \"../spec/index.js\";\nimport { listCapabilities } from \"../knowledge/index.js\";\n\nexport interface TaskSummary {\n total: number;\n pending: number;\n inProgress: number;\n passed: number;\n failed: number;\n blocked: number;\n completionPercent: number;\n}\n\nexport interface ProjectSummary {\n activeChanges: number;\n archivedChanges: number;\n totalCapabilities: number;\n taskSummary: TaskSummary;\n changes: Array<{\n name: string;\n status: string;\n taskSummary: TaskSummary;\n }>;\n}\n\nexport function summarizeTasks(tasks: Task[]): TaskSummary {\n const total = tasks.length;\n if (total === 0) {\n return {\n total: 0,\n pending: 0,\n inProgress: 0,\n passed: 0,\n failed: 0,\n blocked: 0,\n completionPercent: 0,\n };\n }\n\n const pending = tasks.filter((t) => t.status === \"pending\").length;\n const inProgress = tasks.filter(\n (t) =>\n t.status === \"claimed\" ||\n t.status === \"in-progress\" ||\n t.status === \"implemented\" ||\n t.status === \"validating\"\n ).length;\n const passed = tasks.filter((t) => t.status === \"passed\").length;\n const failed = tasks.filter((t) => t.status === \"failed\").length;\n const blocked = tasks.filter((t) => t.status === \"blocked\").length;\n\n const completionPercent = Math.round((passed / total) * 100);\n\n return { total, pending, inProgress, passed, failed, blocked, completionPercent };\n}\n\nexport async function getProjectSummary(\n projectRoot: string\n): Promise<ProjectSummary> {\n const activeChanges = await listChanges(projectRoot, false);\n const allChanges = await listChanges(projectRoot, true);\n const archivedChanges = allChanges.filter((c) => c.status === \"archived\");\n const capabilities = await listCapabilities(projectRoot);\n\n const allTasks = activeChanges.flatMap((c) => c.tasks);\n\n return {\n activeChanges: activeChanges.length,\n archivedChanges: archivedChanges.length,\n totalCapabilities: capabilities.length,\n taskSummary: summarizeTasks(allTasks),\n changes: activeChanges.map((c) => ({\n name: c.name,\n status: c.status,\n taskSummary: summarizeTasks(c.tasks),\n })),\n };\n}\n"],"mappings":"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","names":["readFile","writeFile","join","join","writeFile","readFile","readFile","writeFile","readdir","mkdir","join","join","readFile","readdir","mkdir","writeFile","readFile","join","join","readFile"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgelore/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Spec engine for forgelore: parsing, validation, drift detection, knowledge management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"dev": "tsup --watch",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"clean": "rm -rf dist"
|
|
20
|
+
},
|
|
21
|
+
"files": ["dist"],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8.4.0",
|
|
25
|
+
"typescript": "^5.8.0",
|
|
26
|
+
"vitest": "^3.1.0"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"registry": "https://registry.npmjs.org/"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/DxVapor/forgelore.git",
|
|
35
|
+
"directory": "packages/core"
|
|
36
|
+
}
|
|
37
|
+
}
|