Bump version to 2.35.0: Knowledge Resilience & Standardization Milestone.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { ProjectProfile } from './types';
|
||||
|
||||
export function buildProjectChronicleGuardContext(project: ProjectProfile | null): string {
|
||||
const hasUsableProject = !!project?.recordRoot?.trim();
|
||||
const projectLines = project ? [
|
||||
`Project selection status: selected`,
|
||||
`Active record project: ${project.projectName}`,
|
||||
`Project root: ${project.projectRoot || 'Not set'}`,
|
||||
`Record root: ${project.recordRoot}`,
|
||||
`Core purpose: ${project.corePurpose || project.description || 'Not captured yet.'}`,
|
||||
`Record detail level: ${project.detailLevel}`
|
||||
] : [
|
||||
'Project selection status: not selected',
|
||||
'No active record project is selected. Before writing records, ask the user to select or create one.'
|
||||
];
|
||||
|
||||
return [
|
||||
...projectLines,
|
||||
'',
|
||||
'This guard is active for project ideas, feature requests, architecture proposals, implementation planning, and design decisions.',
|
||||
'',
|
||||
'Required response order for new ideas or feature requests:',
|
||||
'1. Request summary.',
|
||||
'2. Inferred user intent.',
|
||||
'3. Project record target check. If no project is selected, ask whether to use an existing project, create a new project, or skip recording.',
|
||||
'4. Record path check. If no record root is available, say a Markdown record path is required before writing files.',
|
||||
'5. Ask only 1 to 3 blocking questions.',
|
||||
'6. For every question, include "Question reason" explaining why it changes storage, scope, dependencies, or implementation direction.',
|
||||
'7. Direction review focused on project fit and dependency risk.',
|
||||
'8. Recommend a low-dependency MVP first.',
|
||||
'9. Put Vector DB, relational DB, knowledge graph, semantic search, and complex automation only under "Later expansion" unless the user explicitly asks for them now.',
|
||||
'10. End with "Candidate records for this discussion" and list planning, discussions, decisions, development, bugs, or retrospectives paths as candidates.',
|
||||
'',
|
||||
'Decision policy:',
|
||||
'- Do not mark a decision as accepted until the user confirms it.',
|
||||
'- Before confirmation, call decisions "candidates" or "pending".',
|
||||
'- Prefer "reduced adoption" when the idea is useful but too large for the MVP.',
|
||||
'',
|
||||
'Tone and scope:',
|
||||
'- Be practical and plain-spoken.',
|
||||
'- Avoid grand phrases like advanced cognitive architecture, compounding knowledge, perfect graph, or ultimate knowledge distiller.',
|
||||
'- When the user wants low dependency, keep the first proposal to Markdown, JSON, local files, and explicit user save actions.',
|
||||
'- Do not jump directly to large architectures. Narrow direction before expanding.',
|
||||
'',
|
||||
hasUsableProject
|
||||
? 'The current project and record root are available, so candidate record paths may use this project.'
|
||||
: 'The project or record root is missing, so candidate records must be described but not written.',
|
||||
'Do not claim a Markdown file was written unless a tool or explicit sidebar action actually wrote it.'
|
||||
].join('\n');
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { MarkdownFileWriter } from './markdownFileWriter';
|
||||
import {
|
||||
BugRecord,
|
||||
ChronicleRecordEntry,
|
||||
ChronicleWriteResult,
|
||||
DecisionRecord,
|
||||
DevelopmentLog,
|
||||
DiscussionRecord,
|
||||
PlanningDocument,
|
||||
ProjectProfile,
|
||||
RetrospectiveRecord
|
||||
} from './types';
|
||||
import {
|
||||
renderBugRecord,
|
||||
renderDecisionRecord,
|
||||
renderDevelopmentLog,
|
||||
renderDiscussionRecord,
|
||||
renderPlanningDocument,
|
||||
renderProjectProfile,
|
||||
renderProjectReadme,
|
||||
renderRetrospective,
|
||||
renderTimelineSeed
|
||||
} from './templates';
|
||||
|
||||
export * from './types';
|
||||
export * from './guardPrompt';
|
||||
|
||||
const sectionDirs = ['planning', 'discussions', 'decisions', 'development', 'bugs', 'retrospectives'];
|
||||
|
||||
export class ProjectChronicleManager {
|
||||
private readonly writer = new MarkdownFileWriter();
|
||||
|
||||
public ensureProject(profile: ProjectProfile): void {
|
||||
if (!profile.recordRoot || !profile.recordRoot.trim()) {
|
||||
throw new Error('Record root is required before writing chronicle documents.');
|
||||
}
|
||||
|
||||
this.writer.ensureDir(profile.recordRoot);
|
||||
for (const dir of sectionDirs) {
|
||||
this.writer.ensureDir(path.join(profile.recordRoot, dir));
|
||||
}
|
||||
|
||||
this.writeSeedFile(path.join(profile.recordRoot, 'README.md'), renderProjectReadme(profile));
|
||||
this.writeSeedFile(path.join(profile.recordRoot, 'project-profile.md'), renderProjectProfile(profile));
|
||||
this.writeSeedFile(path.join(profile.recordRoot, 'timeline.md'), renderTimelineSeed(profile));
|
||||
this.writeProjectConfig(profile);
|
||||
}
|
||||
|
||||
public writePlanning(profile: ProjectProfile, doc: PlanningDocument): ChronicleWriteResult {
|
||||
this.ensureProject(profile);
|
||||
const date = this.datePart(doc.createdAt);
|
||||
const fileName = `${date}_${this.slug(doc.featureName)}.md`;
|
||||
return this.write(profile, 'planning', fileName, renderPlanningDocument(doc));
|
||||
}
|
||||
|
||||
public writeDiscussion(profile: ProjectProfile, record: DiscussionRecord): ChronicleWriteResult {
|
||||
this.ensureProject(profile);
|
||||
const date = this.datePart(record.createdAt);
|
||||
const fileName = `${date}_${this.slug(record.title)}.md`;
|
||||
return this.write(profile, 'discussions', fileName, renderDiscussionRecord(record));
|
||||
}
|
||||
|
||||
public writeDecision(profile: ProjectProfile, record: DecisionRecord, adrNumber: number): ChronicleWriteResult {
|
||||
this.ensureProject(profile);
|
||||
const padded = String(adrNumber).padStart(4, '0');
|
||||
const fileName = `ADR-${padded}-${this.slug(record.title)}.md`;
|
||||
return this.write(profile, 'decisions', fileName, renderDecisionRecord(record));
|
||||
}
|
||||
|
||||
public writeDevelopmentLog(profile: ProjectProfile, log: DevelopmentLog): ChronicleWriteResult {
|
||||
this.ensureProject(profile);
|
||||
const date = this.datePart(log.createdAt);
|
||||
const fileName = `${date}_${this.slug(log.featureName)}_implementation.md`;
|
||||
return this.write(profile, 'development', fileName, renderDevelopmentLog(log));
|
||||
}
|
||||
|
||||
public writeBug(profile: ProjectProfile, record: BugRecord, bugNumber: number): ChronicleWriteResult {
|
||||
this.ensureProject(profile);
|
||||
const padded = String(bugNumber).padStart(4, '0');
|
||||
const fileName = `BUG-${padded}-${this.slug(record.title)}.md`;
|
||||
return this.write(profile, 'bugs', fileName, renderBugRecord(record));
|
||||
}
|
||||
|
||||
public writeRetrospective(profile: ProjectProfile, record: RetrospectiveRecord): ChronicleWriteResult {
|
||||
this.ensureProject(profile);
|
||||
const date = this.datePart(record.createdAt);
|
||||
const fileName = `${date}_${this.slug(record.title)}.md`;
|
||||
return this.write(profile, 'retrospectives', fileName, renderRetrospective(record));
|
||||
}
|
||||
|
||||
public appendTimeline(profile: ProjectProfile, lines: string[], createdAt: string = new Date().toISOString()): void {
|
||||
this.ensureProject(profile);
|
||||
const timelinePath = path.join(profile.recordRoot, 'timeline.md');
|
||||
const markdown = [
|
||||
'',
|
||||
`## ${this.datePart(createdAt)}`,
|
||||
...lines.map(line => `- ${line}`)
|
||||
].join('\n');
|
||||
this.writer.appendMarkdown(timelinePath, markdown);
|
||||
}
|
||||
|
||||
public nextAdrNumber(profile: ProjectProfile): number {
|
||||
return this.nextNumber(path.join(profile.recordRoot, 'decisions'), /^ADR-(\d+)/);
|
||||
}
|
||||
|
||||
public nextBugNumber(profile: ProjectProfile): number {
|
||||
return this.nextNumber(path.join(profile.recordRoot, 'bugs'), /^BUG-(\d+)/);
|
||||
}
|
||||
|
||||
public listRecords(profile: ProjectProfile): ChronicleRecordEntry[] {
|
||||
this.ensureProject(profile);
|
||||
const records: ChronicleRecordEntry[] = [];
|
||||
|
||||
for (const section of sectionDirs) {
|
||||
const sectionPath = path.join(profile.recordRoot, section);
|
||||
if (!fs.existsSync(sectionPath)) continue;
|
||||
|
||||
for (const fileName of fs.readdirSync(sectionPath)) {
|
||||
if (!fileName.endsWith('.md')) continue;
|
||||
const filePath = path.join(sectionPath, fileName);
|
||||
const stat = fs.statSync(filePath);
|
||||
records.push({
|
||||
section,
|
||||
fileName,
|
||||
filePath,
|
||||
relativePath: path.relative(profile.recordRoot, filePath),
|
||||
updatedAt: stat.mtimeMs
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return records.sort((a, b) => b.updatedAt - a.updatedAt);
|
||||
}
|
||||
|
||||
private write(profile: ProjectProfile, section: string, fileName: string, markdown: string): ChronicleWriteResult {
|
||||
const filePath = this.writer.writeMarkdown(path.join(profile.recordRoot, section, fileName), markdown);
|
||||
return {
|
||||
filePath,
|
||||
relativePath: path.relative(profile.recordRoot, filePath)
|
||||
};
|
||||
}
|
||||
|
||||
private writeSeedFile(filePath: string, content: string): void {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
this.writer.writeMarkdown(filePath, content);
|
||||
}
|
||||
}
|
||||
|
||||
private writeProjectConfig(profile: ProjectProfile): void {
|
||||
const configPath = path.join(profile.recordRoot, 'chronicle.config.json');
|
||||
const config = {
|
||||
projectId: profile.projectId,
|
||||
projectName: profile.projectName,
|
||||
projectRoot: profile.projectRoot || '',
|
||||
recordRoot: profile.recordRoot,
|
||||
description: profile.description || '',
|
||||
corePurpose: profile.corePurpose || '',
|
||||
detailLevel: profile.detailLevel,
|
||||
createdAt: profile.createdAt,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
||||
}
|
||||
|
||||
private nextNumber(dir: string, pattern: RegExp): number {
|
||||
if (!fs.existsSync(dir)) return 1;
|
||||
const max = fs.readdirSync(dir).reduce((current, fileName) => {
|
||||
const match = fileName.match(pattern);
|
||||
if (!match) return current;
|
||||
return Math.max(current, Number(match[1]) || 0);
|
||||
}, 0);
|
||||
return max + 1;
|
||||
}
|
||||
|
||||
private slug(value: string): string {
|
||||
const slug = value
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9가-힣]+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
.slice(0, 60);
|
||||
return slug || 'record';
|
||||
}
|
||||
|
||||
private datePart(iso?: string): string {
|
||||
return (iso || new Date().toISOString()).slice(0, 10);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export class MarkdownFileWriter {
|
||||
public ensureDir(dirPath: string): void {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
public writeMarkdown(filePath: string, content: string): string {
|
||||
this.ensureDir(path.dirname(filePath));
|
||||
const finalPath = this.getAvailablePath(filePath);
|
||||
fs.writeFileSync(finalPath, this.normalize(content), 'utf8');
|
||||
return finalPath;
|
||||
}
|
||||
|
||||
public appendMarkdown(filePath: string, content: string): void {
|
||||
this.ensureDir(path.dirname(filePath));
|
||||
fs.appendFileSync(filePath, this.normalize(content), 'utf8');
|
||||
}
|
||||
|
||||
private getAvailablePath(filePath: string): string {
|
||||
if (!fs.existsSync(filePath)) return filePath;
|
||||
|
||||
const dir = path.dirname(filePath);
|
||||
const ext = path.extname(filePath);
|
||||
const base = path.basename(filePath, ext);
|
||||
let index = 2;
|
||||
|
||||
while (true) {
|
||||
const candidate = path.join(dir, `${base}-${index}${ext}`);
|
||||
if (!fs.existsSync(candidate)) return candidate;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
private normalize(content: string): string {
|
||||
return content.replace(/\r\n/g, '\n').trimEnd() + '\n';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
import {
|
||||
BugRecord,
|
||||
DecisionRecord,
|
||||
DevelopmentLog,
|
||||
DiscussionRecord,
|
||||
PlanningDocument,
|
||||
ProjectProfile,
|
||||
RetrospectiveRecord
|
||||
} from './types';
|
||||
|
||||
const list = (items: string[] | undefined, fallback: string = 'Not captured yet.') => {
|
||||
if (!items || items.length === 0) return fallback;
|
||||
return items.map(item => `- ${item}`).join('\n');
|
||||
};
|
||||
|
||||
const dateOnly = (iso?: string) => (iso || new Date().toISOString()).slice(0, 10);
|
||||
|
||||
export function renderProjectReadme(profile: ProjectProfile): string {
|
||||
return [
|
||||
`# ${profile.projectName} Chronicle Records`,
|
||||
'',
|
||||
'## Project',
|
||||
`- ID: ${profile.projectId}`,
|
||||
`- Root: ${profile.projectRoot || 'Not set'}`,
|
||||
`- Record root: ${profile.recordRoot}`,
|
||||
`- Detail level: ${profile.detailLevel}`,
|
||||
'',
|
||||
'## Purpose',
|
||||
profile.corePurpose || profile.description || 'Not captured yet.',
|
||||
'',
|
||||
'## Folders',
|
||||
'- `planning/`',
|
||||
'- `discussions/`',
|
||||
'- `decisions/`',
|
||||
'- `development/`',
|
||||
'- `bugs/`',
|
||||
'- `retrospectives/`'
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function renderProjectProfile(profile: ProjectProfile): string {
|
||||
return [
|
||||
'# Project Profile',
|
||||
'',
|
||||
'## Project Name',
|
||||
profile.projectName,
|
||||
'',
|
||||
'## Description',
|
||||
profile.description || 'Not captured yet.',
|
||||
'',
|
||||
'## Project Root',
|
||||
profile.projectRoot || 'Not set',
|
||||
'',
|
||||
'## Record Root',
|
||||
profile.recordRoot,
|
||||
'',
|
||||
'## Core Purpose',
|
||||
profile.corePurpose || 'Not captured yet.',
|
||||
'',
|
||||
'## Target Users',
|
||||
list(profile.targetUsers),
|
||||
'',
|
||||
'## Avoid Directions',
|
||||
list(profile.avoidDirections),
|
||||
'',
|
||||
'## Record Detail Level',
|
||||
profile.detailLevel,
|
||||
'',
|
||||
'## Created',
|
||||
profile.createdAt,
|
||||
'',
|
||||
'## Updated',
|
||||
profile.updatedAt
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function renderTimelineSeed(profile: ProjectProfile): string {
|
||||
return [
|
||||
'# Project Timeline',
|
||||
'',
|
||||
`## ${dateOnly(profile.createdAt)}`,
|
||||
`- Project Chronicle record folder initialized for ${profile.projectName}.`
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function renderPlanningDocument(doc: PlanningDocument): string {
|
||||
return [
|
||||
`# Feature Plan: ${doc.featureName}`,
|
||||
'',
|
||||
'## 1. Feature Name',
|
||||
doc.featureName,
|
||||
'',
|
||||
'## 2. Reason',
|
||||
doc.purpose,
|
||||
'',
|
||||
'## 3. Original User Request',
|
||||
doc.sourceRequest || 'Not captured yet.',
|
||||
'',
|
||||
'## 4. Interpreted User Intent',
|
||||
doc.userIntent,
|
||||
'',
|
||||
'## 5. Background',
|
||||
doc.background,
|
||||
'',
|
||||
'## 6. Scope',
|
||||
list(doc.scope),
|
||||
'',
|
||||
'## 7. Out Of Scope',
|
||||
list(doc.outOfScope),
|
||||
'',
|
||||
'## 8. Development Direction',
|
||||
doc.developmentDirection,
|
||||
'',
|
||||
'## 9. Dependency Strategy',
|
||||
doc.dependencyStrategy,
|
||||
'',
|
||||
'## 10. Expected Value',
|
||||
doc.expectedValue,
|
||||
'',
|
||||
'## 11. Success Criteria',
|
||||
list(doc.successCriteria),
|
||||
'',
|
||||
'## 12. Developer Instruction',
|
||||
doc.developerInstruction
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function renderDiscussionRecord(record: DiscussionRecord): string {
|
||||
const questions = record.questions.length
|
||||
? record.questions.map((q, index) => [
|
||||
`### Question ${index + 1}`,
|
||||
q.question,
|
||||
'',
|
||||
'### Reason',
|
||||
q.reason,
|
||||
'',
|
||||
'### Expected Information',
|
||||
q.expectedInformation,
|
||||
'',
|
||||
'### Impact On Decision',
|
||||
q.impactOnDecision,
|
||||
'',
|
||||
q.userAnswer ? `### User Answer\n${q.userAnswer}` : '',
|
||||
q.result ? `### Result\n${q.result}` : ''
|
||||
].filter(Boolean).join('\n')).join('\n\n')
|
||||
: 'No explicit question was captured.';
|
||||
|
||||
return [
|
||||
`# Discussion: ${record.title}`,
|
||||
'',
|
||||
'## User Request Summary',
|
||||
record.userRequest,
|
||||
'',
|
||||
'## Interpreted Intent',
|
||||
record.interpretedIntent,
|
||||
'',
|
||||
'## Questions',
|
||||
questions,
|
||||
'',
|
||||
'## Main Discussion',
|
||||
list(record.discussions),
|
||||
'',
|
||||
'## Decisions',
|
||||
record.decisions.length
|
||||
? record.decisions.map(decision => `- ${decision.title}: ${decision.decision}`).join('\n')
|
||||
: 'No decisions captured yet.'
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function renderDecisionRecord(record: DecisionRecord): string {
|
||||
return [
|
||||
`# ADR: ${record.title}`,
|
||||
'',
|
||||
'## Status',
|
||||
record.status,
|
||||
'',
|
||||
'## Context',
|
||||
record.context,
|
||||
'',
|
||||
'## Decision',
|
||||
record.decision,
|
||||
'',
|
||||
'## Reason',
|
||||
record.reason,
|
||||
'',
|
||||
'## Alternatives',
|
||||
list(record.alternatives),
|
||||
'',
|
||||
'## Consequences',
|
||||
list(record.consequences)
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function renderDevelopmentLog(log: DevelopmentLog): string {
|
||||
return [
|
||||
`# Development Log: ${log.featureName}`,
|
||||
'',
|
||||
'## Purpose',
|
||||
log.purpose,
|
||||
'',
|
||||
'## Implementation Summary',
|
||||
log.implementationSummary,
|
||||
'',
|
||||
'## Architecture',
|
||||
log.architecture,
|
||||
'',
|
||||
'## Changed Files',
|
||||
list(log.changedFiles),
|
||||
'',
|
||||
'## Dependency Notes',
|
||||
log.dependencyNotes,
|
||||
'',
|
||||
'## Bugs',
|
||||
log.bugs.length ? log.bugs.map(bug => `- ${bug.title}: ${bug.symptom}`).join('\n') : 'No bugs recorded.',
|
||||
'',
|
||||
'## Lessons',
|
||||
list(log.lessons)
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function renderBugRecord(record: BugRecord): string {
|
||||
return [
|
||||
`# Bug: ${record.title}`,
|
||||
'',
|
||||
'## Date',
|
||||
dateOnly(record.createdAt),
|
||||
'',
|
||||
'## Symptom',
|
||||
record.symptom,
|
||||
'',
|
||||
'## Cause',
|
||||
record.cause,
|
||||
'',
|
||||
'## Fix',
|
||||
record.fix,
|
||||
'',
|
||||
'## Prevention',
|
||||
record.prevention
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function renderRetrospective(record: RetrospectiveRecord): string {
|
||||
return [
|
||||
`# Retrospective: ${record.title}`,
|
||||
'',
|
||||
'## Summary',
|
||||
record.summary,
|
||||
'',
|
||||
'## Went Well',
|
||||
list(record.wentWell),
|
||||
'',
|
||||
'## To Improve',
|
||||
list(record.toImprove),
|
||||
'',
|
||||
'## Next Actions',
|
||||
list(record.nextActions)
|
||||
].join('\n');
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
export type ChronicleDetailLevel = 'simple' | 'standard' | 'detailed';
|
||||
|
||||
export interface ProjectProfile {
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
projectRoot?: string;
|
||||
recordRoot: string;
|
||||
description?: string;
|
||||
corePurpose?: string;
|
||||
targetUsers?: string[];
|
||||
avoidDirections?: string[];
|
||||
detailLevel: ChronicleDetailLevel;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface QuestionRecord {
|
||||
question: string;
|
||||
reason: string;
|
||||
expectedInformation: string;
|
||||
impactOnDecision: string;
|
||||
userAnswer?: string;
|
||||
result?: string;
|
||||
}
|
||||
|
||||
export interface DecisionRecord {
|
||||
title: string;
|
||||
status: 'accepted' | 'rejected' | 'deferred' | 'changed';
|
||||
context: string;
|
||||
decision: string;
|
||||
reason: string;
|
||||
alternatives?: string[];
|
||||
consequences?: string[];
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface PlanningDocument {
|
||||
featureName: string;
|
||||
purpose: string;
|
||||
background: string;
|
||||
userIntent: string;
|
||||
scope: string[];
|
||||
outOfScope: string[];
|
||||
developmentDirection: string;
|
||||
dependencyStrategy: string;
|
||||
expectedValue: string;
|
||||
successCriteria: string[];
|
||||
developerInstruction: string;
|
||||
sourceRequest?: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface DiscussionRecord {
|
||||
title: string;
|
||||
userRequest: string;
|
||||
interpretedIntent: string;
|
||||
questions: QuestionRecord[];
|
||||
discussions: string[];
|
||||
decisions: DecisionRecord[];
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface DevelopmentLog {
|
||||
featureName: string;
|
||||
purpose: string;
|
||||
implementationSummary: string;
|
||||
architecture: string;
|
||||
changedFiles: string[];
|
||||
dependencyNotes: string;
|
||||
bugs: BugRecord[];
|
||||
lessons: string[];
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface BugRecord {
|
||||
title: string;
|
||||
symptom: string;
|
||||
cause: string;
|
||||
fix: string;
|
||||
prevention: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface RetrospectiveRecord {
|
||||
title: string;
|
||||
summary: string;
|
||||
wentWell: string[];
|
||||
toImprove: string[];
|
||||
nextActions: string[];
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface ChronicleWriteResult {
|
||||
filePath: string;
|
||||
relativePath: string;
|
||||
}
|
||||
|
||||
export interface ChronicleRecordEntry {
|
||||
section: string;
|
||||
fileName: string;
|
||||
filePath: string;
|
||||
relativePath: string;
|
||||
updatedAt: number;
|
||||
}
|
||||
Reference in New Issue
Block a user