project-logbook 0.3.4 → 0.4.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/commands/build.js +22 -1
- package/dist/commands/lint.js +18 -0
- package/dist/commands/status.js +23 -7
- package/dist/lib/build-helpers.js +7 -22
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.js +7 -3
- package/dist/lib/git-helpers.d.ts +21 -0
- package/dist/lib/git-helpers.js +91 -0
- package/dist/lib/image-helpers.js +2 -2
- package/dist/lib/markdown-processors.d.ts +5 -1
- package/dist/lib/markdown-processors.js +28 -2
- package/dist/lib/rss.d.ts +29 -0
- package/dist/lib/rss.js +77 -0
- package/dist/lib/template-helpers.d.ts +3 -3
- package/dist/lib/template-helpers.js +19 -6
- package/dist/lib/template-types.d.ts +2 -0
- package/dist/lib/templates.d.ts +5 -2
- package/dist/lib/templates.js +10 -7
- package/dist/linters/index.js +2 -0
- package/dist/linters/technical-log.d.ts +7 -0
- package/dist/linters/technical-log.js +72 -0
- package/dist/templates/index.md +4 -0
- package/dist/templates/log.md +5 -0
- package/dist/templates/steer.txt +21 -5
- package/dist/templates/styles.css +116 -0
- package/dist/utils/date.d.ts +7 -0
- package/dist/utils/date.js +12 -0
- package/dist/utils/log-timeline.d.ts +69 -0
- package/dist/utils/log-timeline.js +218 -0
- package/package.json +3 -1
- package/src/templates/index.md +4 -0
- package/src/templates/log.md +5 -0
- package/src/templates/steer.txt +21 -5
- package/src/templates/styles.css +116 -0
|
@@ -18,6 +18,7 @@ export interface TimelineTemplateProps {
|
|
|
18
18
|
aboutHtml: string;
|
|
19
19
|
jiraBaseUrl?: string;
|
|
20
20
|
jiraPrefix?: string;
|
|
21
|
+
repositoryUrl?: string;
|
|
21
22
|
currentPage?: number;
|
|
22
23
|
totalPages?: number;
|
|
23
24
|
}
|
|
@@ -101,6 +102,7 @@ export interface PostTemplateProps {
|
|
|
101
102
|
commits?: GitCommit[];
|
|
102
103
|
version: string;
|
|
103
104
|
jiraUrl?: string;
|
|
105
|
+
repositoryUrl?: string;
|
|
104
106
|
prevEntry: EntryLink | null;
|
|
105
107
|
nextEntry: EntryLink | null;
|
|
106
108
|
slug?: string;
|
package/dist/lib/templates.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TabConfig, TimelineTemplateProps, PostTemplateProps } from './template-types.js';
|
|
2
2
|
export declare const tabsComponent: (tabs: TabConfig[], defaultActive?: number) => string;
|
|
3
|
-
export declare const layout: ({ title, header, content, projectName, basePath, description, bodySlug, buildMeta, }: {
|
|
3
|
+
export declare const layout: ({ title, header, content, projectName, basePath, description, bodySlug, buildMeta, includeRssLink, }: {
|
|
4
4
|
title: string;
|
|
5
5
|
header: string;
|
|
6
6
|
content: string;
|
|
@@ -9,8 +9,11 @@ export declare const layout: ({ title, header, content, projectName, basePath, d
|
|
|
9
9
|
description?: string;
|
|
10
10
|
bodySlug?: string;
|
|
11
11
|
buildMeta?: string;
|
|
12
|
+
includeRssLink?: boolean;
|
|
12
13
|
}) => string;
|
|
13
|
-
export declare const timelineTemplate: (props: TimelineTemplateProps
|
|
14
|
+
export declare const timelineTemplate: (props: TimelineTemplateProps & {
|
|
15
|
+
includeRssLink?: boolean;
|
|
16
|
+
}) => {
|
|
14
17
|
header: string;
|
|
15
18
|
content: string;
|
|
16
19
|
};
|
package/dist/lib/templates.js
CHANGED
|
@@ -13,22 +13,25 @@ export const tabsComponent = (tabs, defaultActive = 0) => {
|
|
|
13
13
|
${sectionsHtml}
|
|
14
14
|
</div>`;
|
|
15
15
|
};
|
|
16
|
-
export const layout = ({ title, header, content, projectName, basePath = './', description, bodySlug, buildMeta, }) => html `<!DOCTYPE html>
|
|
16
|
+
export const layout = ({ title, header, content, projectName, basePath = './', description, bodySlug, buildMeta, includeRssLink = false, }) => html `<!DOCTYPE html>
|
|
17
17
|
<html lang="en">
|
|
18
18
|
<head>
|
|
19
19
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
20
20
|
<title>${escapeHtml(title)} | ${escapeHtml(projectName)}</title>
|
|
21
21
|
${description ? `<meta name="description" content="${escapeHtml(description).replace(/"/g, '"').substring(0, 160)}">` : ''}
|
|
22
|
+
${includeRssLink ? `<link rel="alternate" type="application/rss+xml" title="Subscribe to Project RSS Feed" href="${basePath}rss.xml">` : ''}
|
|
22
23
|
<link rel="icon" type="image/svg+xml" href="${basePath}favicon.svg">
|
|
23
24
|
<link rel="stylesheet" href="${basePath}style.css">
|
|
24
25
|
</head>
|
|
25
26
|
<body${bodySlug ? ` ${ATTR_PAGE_SLUG}="${bodySlug}"` : ''}>
|
|
26
27
|
${header}<main>${content}</main>
|
|
27
|
-
<footer
|
|
28
|
+
<footer>
|
|
29
|
+
${buildMeta ? `<p class="build-time">${buildMeta}${includeRssLink ? ` • <a href="${basePath}rss.xml">RSS Feed</a>` : ''}</p>` : ''}
|
|
30
|
+
</footer>
|
|
28
31
|
<script src="${basePath}logbook.js"></script>
|
|
29
32
|
</body></html>`;
|
|
30
33
|
export const timelineTemplate = (props) => {
|
|
31
|
-
const { projectName, groups, readmeHtml, aboutHtml, jiraBaseUrl, jiraPrefix, currentPage, totalPages } = props;
|
|
34
|
+
const { projectName, groups, readmeHtml, aboutHtml, jiraBaseUrl, jiraPrefix, repositoryUrl, currentPage, totalPages, } = props;
|
|
32
35
|
const timelineTab = html `<div class="timeline">
|
|
33
36
|
${groups
|
|
34
37
|
.map((g) => html `<section class="month-group">
|
|
@@ -38,8 +41,8 @@ export const timelineTemplate = (props) => {
|
|
|
38
41
|
if (item.kind === 'entry')
|
|
39
42
|
return renderTimelineEntryItem(item);
|
|
40
43
|
if (item.kind === 'tag')
|
|
41
|
-
return renderTimelineTagItem(item);
|
|
42
|
-
return renderTimelineCommitItem(item, jiraBaseUrl, jiraPrefix);
|
|
44
|
+
return renderTimelineTagItem(item, repositoryUrl);
|
|
45
|
+
return renderTimelineCommitItem(item, jiraBaseUrl, jiraPrefix, repositoryUrl);
|
|
43
46
|
})
|
|
44
47
|
.join('')}
|
|
45
48
|
</section>`)
|
|
@@ -58,7 +61,7 @@ export const timelineTemplate = (props) => {
|
|
|
58
61
|
return { header, content };
|
|
59
62
|
};
|
|
60
63
|
export const postTemplate = (props) => {
|
|
61
|
-
const { title, displayDate, dateStart, harness, llm, prompter, content, ticketHtml, logHtml, commits, version, jiraUrl, prevEntry, nextEntry, tags, workspaces } = props; // prettier-ignore
|
|
64
|
+
const { title, displayDate, dateStart, harness, llm, prompter, content, ticketHtml, logHtml, commits, version, jiraUrl, repositoryUrl, prevEntry, nextEntry, tags, workspaces } = props; // prettier-ignore
|
|
62
65
|
// Validate jiraUrl scheme to prevent javascript: URLs
|
|
63
66
|
const validatedJiraUrl = jiraUrl ? validateUrlScheme(jiraUrl) : undefined;
|
|
64
67
|
const jiraBtn = validatedJiraUrl
|
|
@@ -89,7 +92,7 @@ export const postTemplate = (props) => {
|
|
|
89
92
|
</div>
|
|
90
93
|
<div id="spec" class="content-section" role="tabpanel" style="display:none;">${ticketHtml}</div>
|
|
91
94
|
<div id="log" class="content-section" role="tabpanel" style="display:none;">
|
|
92
|
-
${logHtml}${renderCommits(commits)}
|
|
95
|
+
${logHtml}${renderCommits(commits, repositoryUrl)}
|
|
93
96
|
</div>
|
|
94
97
|
</div>
|
|
95
98
|
<nav class="post-nav">
|
package/dist/linters/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import lockfile from './lockfile.js';
|
|
|
6
6
|
import placeholders from './placeholders.js';
|
|
7
7
|
import projectIntegrity from './project-integrity.js';
|
|
8
8
|
import readability from './readability.js';
|
|
9
|
+
import technicalLog from './technical-log.js';
|
|
9
10
|
import workspaces from './workspaces.js';
|
|
10
11
|
export const linters = [
|
|
11
12
|
diffToNarrative,
|
|
@@ -16,5 +17,6 @@ export const linters = [
|
|
|
16
17
|
placeholders,
|
|
17
18
|
projectIntegrity,
|
|
18
19
|
readability,
|
|
20
|
+
technicalLog,
|
|
19
21
|
workspaces,
|
|
20
22
|
];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Linter that checks for adequate technical logging.
|
|
5
|
+
* Ensures log.md has more than just the initial "Started investigation" entry.
|
|
6
|
+
*/
|
|
7
|
+
const technicalLog = {
|
|
8
|
+
name: 'technical-log',
|
|
9
|
+
description: 'Checks for adequate technical logging in log.md',
|
|
10
|
+
async check(context) {
|
|
11
|
+
const issues = [];
|
|
12
|
+
// Only check the active entry (the one with the lockfile)
|
|
13
|
+
// Old entries cannot be updated, so we don't warn about them
|
|
14
|
+
if (!context.entryName || !context.entryPath) {
|
|
15
|
+
return issues;
|
|
16
|
+
}
|
|
17
|
+
// Check if this is the active entry by verifying lockfile exists
|
|
18
|
+
const lockfilePath = join(process.cwd(), '.logbook-active');
|
|
19
|
+
if (!(await fs.pathExists(lockfilePath))) {
|
|
20
|
+
// No active entry, skip this linter
|
|
21
|
+
return issues;
|
|
22
|
+
}
|
|
23
|
+
const lockfileContent = await fs.readFile(lockfilePath, 'utf8');
|
|
24
|
+
const lockfileData = JSON.parse(lockfileContent);
|
|
25
|
+
const activeEntrySlug = lockfileData.slug;
|
|
26
|
+
// Only check if this entry matches the active entry slug
|
|
27
|
+
if (context.entryName !== activeEntrySlug) {
|
|
28
|
+
return issues;
|
|
29
|
+
}
|
|
30
|
+
const logPath = join(context.entryPath, 'log.md');
|
|
31
|
+
if (!(await fs.pathExists(logPath))) {
|
|
32
|
+
issues.push({
|
|
33
|
+
category: 'TECHNICAL_LOG',
|
|
34
|
+
level: 'warning',
|
|
35
|
+
message: 'Missing log.md file. Create one to document your technical decisions.',
|
|
36
|
+
});
|
|
37
|
+
return issues;
|
|
38
|
+
}
|
|
39
|
+
const logContent = await fs.readFile(logPath, 'utf8');
|
|
40
|
+
// Extract all protocol entries (lines starting with "- " under ## Protocol)
|
|
41
|
+
const protocolSection = logContent.split('## Protocol')[1];
|
|
42
|
+
if (!protocolSection) {
|
|
43
|
+
// No Protocol section found, might be malformed
|
|
44
|
+
issues.push({
|
|
45
|
+
category: 'TECHNICAL_LOG',
|
|
46
|
+
level: 'warning',
|
|
47
|
+
message: 'log.md missing "## Protocol" section. Add one to structure your log entries.',
|
|
48
|
+
});
|
|
49
|
+
return issues;
|
|
50
|
+
}
|
|
51
|
+
const lines = protocolSection.split('\n').filter((line) => line.trim().startsWith('- '));
|
|
52
|
+
const entryCount = lines.length;
|
|
53
|
+
// Check if there's only the initial "Started investigation" entry
|
|
54
|
+
if (entryCount <= 1) {
|
|
55
|
+
issues.push({
|
|
56
|
+
category: 'TECHNICAL_LOG',
|
|
57
|
+
level: 'warning',
|
|
58
|
+
message: 'log.md only contains the initial "Started investigation" entry. Log your work in real-time using `logbook log "<message>"` after each significant step.',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else if (entryCount < 5) {
|
|
62
|
+
// Less than 5 entries might indicate insufficient logging
|
|
63
|
+
issues.push({
|
|
64
|
+
category: 'TECHNICAL_LOG',
|
|
65
|
+
level: 'warning',
|
|
66
|
+
message: `log.md has only ${entryCount} entries. Consider adding more detail about your implementation process, decisions, and challenges.`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return issues;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
export default technicalLog;
|
package/dist/templates/index.md
CHANGED
|
@@ -21,6 +21,10 @@ dateEnd: "[DATE_END]"
|
|
|
21
21
|
## Summary
|
|
22
22
|
TODO: Write a polished, highly readable ticket summary that reads like an engaging technical narrative (similar to a well-written dev blog post).
|
|
23
23
|
|
|
24
|
+
### Before You Start:
|
|
25
|
+
- **Check `log.md`**: Review your technical log for all the decisions, errors, and pivots you recorded during implementation.
|
|
26
|
+
- **Reference the log**: Use your real-time log entries as source material for the narrative — don't try to reconstruct from memory.
|
|
27
|
+
|
|
24
28
|
### Formatting & Style Rules:
|
|
25
29
|
- **Maintain the narrative tone:** TODO: Keep the storytelling flair (e.g., describing how problems accumulated or how gaps surfaced), but stay strictly factual based on the provided changes.
|
|
26
30
|
- **Add thematic headings:** TODO: Break the narrative down into logical chapters using Markdown headings (e.g., ### The Friction Points, ### The Fix, ### Closing the Gap).
|
package/dist/templates/log.md
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
# Technical Log: {{id}}-{{slug}}
|
|
2
2
|
|
|
3
|
+
> **REMINDER**: Log your work in real-time using `logbook log "<message>"`. Don't wait until the end!
|
|
4
|
+
>
|
|
5
|
+
> Log after every significant step: investigation, errors, decisions, code changes, test runs, etc.
|
|
6
|
+
>
|
|
7
|
+
|
|
3
8
|
## Protocol
|
|
4
9
|
- {{fullIso}}: Started investigation.
|
package/dist/templates/steer.txt
CHANGED
|
@@ -8,11 +8,27 @@ Phase 1: Understand
|
|
|
8
8
|
2. Review `AGENTS.md` and `CONTRIBUTING.md` if you need architectural or workflow context.
|
|
9
9
|
|
|
10
10
|
Phase 2: Execute & Trace
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
|
|
12
|
+
**CRITICAL: Real-Time Logging**
|
|
13
|
+
You MUST log your work continuously using `logbook log "<message>"`. This is not optional.
|
|
14
|
+
|
|
15
|
+
1. Use `log.md` as your live technical scratchpad.
|
|
16
|
+
2. After EVERY significant step, run: `logbook log "<what you just did>"`
|
|
17
|
+
- Investigated a file? Log it.
|
|
18
|
+
- Found an error? Log it.
|
|
19
|
+
- Made a design decision? Log it.
|
|
20
|
+
- Fixed a bug? Log it.
|
|
21
|
+
- Ran tests? Log the result.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
- `logbook log "Investigated src/lib/config.ts - found missing validation"`
|
|
25
|
+
- `logbook log "Created new RSS module with generateRssFeed() function"`
|
|
26
|
+
- `logbook log "Test failed: TypeScript error on line 42 - fixed type assertion"`
|
|
27
|
+
- `logbook log "All 127 tests pass, pre-commit suite successful"`
|
|
28
|
+
|
|
29
|
+
3. **One message per command call.** For multiple entries, call the command multiple times.
|
|
30
|
+
4. Do NOT reconstruct the log at the end. If the log is empty when you finish, you did it wrong.
|
|
31
|
+
5. Stick to the active entry; never modify past entries in the logbook folder.
|
|
16
32
|
|
|
17
33
|
Phase 3: Synthesize
|
|
18
34
|
1. When implementation is finished, write the narrative in `index.md`.
|
|
@@ -696,3 +696,119 @@ article img[src$='.webp'],
|
|
|
696
696
|
article img[src$='.svg'] {
|
|
697
697
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
698
698
|
}
|
|
699
|
+
|
|
700
|
+
/* Technical Log Timeline */
|
|
701
|
+
#log .timeline {
|
|
702
|
+
list-style: none;
|
|
703
|
+
padding: 0;
|
|
704
|
+
margin: 0;
|
|
705
|
+
position: relative;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
#log .timeline::before {
|
|
709
|
+
content: '';
|
|
710
|
+
position: absolute;
|
|
711
|
+
left: 8px;
|
|
712
|
+
top: 0;
|
|
713
|
+
bottom: 0;
|
|
714
|
+
width: 2px;
|
|
715
|
+
background: var(--border);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
#log .timeline-entry {
|
|
719
|
+
position: relative;
|
|
720
|
+
padding-left: 2rem;
|
|
721
|
+
margin-bottom: 0.75rem;
|
|
722
|
+
min-height: 1.5rem;
|
|
723
|
+
display: grid;
|
|
724
|
+
grid-template-columns: 55px 1fr;
|
|
725
|
+
gap: 0.5rem;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
#log .timeline-entry.no-time .timeline-message {
|
|
729
|
+
grid-column: 2;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
#log .timeline-entry.has-time::before {
|
|
733
|
+
content: '';
|
|
734
|
+
position: absolute;
|
|
735
|
+
left: 5px;
|
|
736
|
+
top: 4px;
|
|
737
|
+
width: 8px;
|
|
738
|
+
height: 8px;
|
|
739
|
+
border-radius: 50%;
|
|
740
|
+
background: var(--primary);
|
|
741
|
+
border: 2px solid var(--bg);
|
|
742
|
+
z-index: 1;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
#log .timeline-entry.no-time::before {
|
|
746
|
+
content: '';
|
|
747
|
+
position: absolute;
|
|
748
|
+
left: 7px;
|
|
749
|
+
top: 0.6rem;
|
|
750
|
+
width: 4px;
|
|
751
|
+
height: 4px;
|
|
752
|
+
border-radius: 50%;
|
|
753
|
+
background: var(--text-muted);
|
|
754
|
+
opacity: 0.6;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
#log .timeline-time {
|
|
758
|
+
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace;
|
|
759
|
+
font-size: 0.75rem;
|
|
760
|
+
font-weight: 600;
|
|
761
|
+
color: var(--primary);
|
|
762
|
+
display: inline-block;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
#log .timeline-message {
|
|
766
|
+
font-size: 0.9375rem;
|
|
767
|
+
color: var(--text);
|
|
768
|
+
line-height: 1.5;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
#log .timeline-entry.no-time .timeline-message {
|
|
772
|
+
color: var(--text);
|
|
773
|
+
opacity: 0.85;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
#log .timeline-gap {
|
|
777
|
+
position: relative;
|
|
778
|
+
padding-left: 2rem;
|
|
779
|
+
margin: 1.5rem 0;
|
|
780
|
+
min-height: 1rem;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
#log .timeline-gap::before {
|
|
784
|
+
content: '';
|
|
785
|
+
position: absolute;
|
|
786
|
+
left: 7px;
|
|
787
|
+
top: 0.4rem;
|
|
788
|
+
width: 4px;
|
|
789
|
+
height: 4px;
|
|
790
|
+
border-radius: 50%;
|
|
791
|
+
background: var(--text-muted);
|
|
792
|
+
opacity: 0.5;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
#log .gap-indicator {
|
|
796
|
+
font-size: 0.8125rem;
|
|
797
|
+
color: var(--text-muted);
|
|
798
|
+
font-style: italic;
|
|
799
|
+
display: inline-flex;
|
|
800
|
+
align-items: center;
|
|
801
|
+
gap: 0.25rem;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
#log .log-fallback {
|
|
805
|
+
font-size: 0.9375rem;
|
|
806
|
+
color: var(--text);
|
|
807
|
+
line-height: 1.6;
|
|
808
|
+
white-space: pre-wrap;
|
|
809
|
+
background: var(--primary-soft);
|
|
810
|
+
padding: 1rem;
|
|
811
|
+
border-radius: 0.5rem;
|
|
812
|
+
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace;
|
|
813
|
+
font-size: 0.8125rem;
|
|
814
|
+
}
|
package/dist/utils/date.d.ts
CHANGED
|
@@ -28,3 +28,10 @@ export declare function getSortTime(dateStart: string | Date, dateEnd?: string |
|
|
|
28
28
|
* Alias for formatAbsoluteDate for clarity in build context.
|
|
29
29
|
*/
|
|
30
30
|
export declare function formatDateTimeForDisplay(date: string | Date): string;
|
|
31
|
+
/**
|
|
32
|
+
* Format a date for RSS feed pubDate element.
|
|
33
|
+
* RSS requires RFC 822 date format: "Mon, 26 May 2026 10:51:00 GMT"
|
|
34
|
+
* @param date - The date to format
|
|
35
|
+
* @returns RFC 822 formatted date string
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatDateTimeForRss(date: string | Date): string;
|
package/dist/utils/date.js
CHANGED
|
@@ -86,3 +86,15 @@ export function getSortTime(dateStart, dateEnd) {
|
|
|
86
86
|
export function formatDateTimeForDisplay(date) {
|
|
87
87
|
return formatAbsoluteDate(date);
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Format a date for RSS feed pubDate element.
|
|
91
|
+
* RSS requires RFC 822 date format: "Mon, 26 May 2026 10:51:00 GMT"
|
|
92
|
+
* @param date - The date to format
|
|
93
|
+
* @returns RFC 822 formatted date string
|
|
94
|
+
*/
|
|
95
|
+
export function formatDateTimeForRss(date) {
|
|
96
|
+
const d = parseDate(date);
|
|
97
|
+
if (!d)
|
|
98
|
+
return new Date().toUTCString();
|
|
99
|
+
return d.toUTCString();
|
|
100
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for transforming technical log markdown into a timeline view.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Parsed log entry with timestamp and message.
|
|
6
|
+
*/
|
|
7
|
+
export interface LogEntry {
|
|
8
|
+
timestamp: Date;
|
|
9
|
+
isoTimestamp: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Timeline item for rendering - either a log entry or a gap indicator.
|
|
14
|
+
*/
|
|
15
|
+
export interface TimelineItem {
|
|
16
|
+
type: 'entry' | 'gap';
|
|
17
|
+
timestamp?: Date;
|
|
18
|
+
isoTimestamp?: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
gapHours?: number;
|
|
21
|
+
showTime?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse technical log markdown into structured log entries.
|
|
25
|
+
* @param logMarkdown - The raw markdown content of the technical log
|
|
26
|
+
* @returns Array of parsed log entries, or null if parsing fails
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseLogMarkdown(logMarkdown: string): LogEntry[] | null;
|
|
29
|
+
/**
|
|
30
|
+
* Transform log entries into a timeline with gap indicators.
|
|
31
|
+
* Shows ALL entries, but only displays timestamps for:
|
|
32
|
+
* - First entry
|
|
33
|
+
* - Entries after gaps >= threshold
|
|
34
|
+
* - Last entry
|
|
35
|
+
*
|
|
36
|
+
* This prevents timestamp clutter when LLM writes many entries in quick succession.
|
|
37
|
+
*
|
|
38
|
+
* @param entries - Parsed log entries (sorted chronologically)
|
|
39
|
+
* @param gapThresholdHours - Minimum gap in hours to show timestamp (default: 1)
|
|
40
|
+
* @returns Timeline items for rendering
|
|
41
|
+
*/
|
|
42
|
+
export declare function createTimelineFromEntries(entries: LogEntry[], gapThresholdHours?: number): TimelineItem[];
|
|
43
|
+
/**
|
|
44
|
+
* Render timeline items as HTML.
|
|
45
|
+
* Creates a beautiful timeline that shows:
|
|
46
|
+
* - ALL log entries as messages
|
|
47
|
+
* - Timestamps only for significant moments (start, after gaps, end)
|
|
48
|
+
* - Gap indicators for breaks > threshold
|
|
49
|
+
*
|
|
50
|
+
* @param timeline - Timeline items to render
|
|
51
|
+
* @returns HTML string for the timeline
|
|
52
|
+
*/
|
|
53
|
+
export declare function renderTimelineHtml(timeline: TimelineItem[]): string;
|
|
54
|
+
/**
|
|
55
|
+
* Get the last log entry from technical log markdown.
|
|
56
|
+
* Useful for displaying a "last logged" nudge in CLI output.
|
|
57
|
+
*
|
|
58
|
+
* @param logMarkdown - The raw markdown content of the technical log
|
|
59
|
+
* @returns The last LogEntry, or null if none found
|
|
60
|
+
*/
|
|
61
|
+
export declare function getLastLogEntry(logMarkdown: string): LogEntry | null;
|
|
62
|
+
/**
|
|
63
|
+
* Main function to transform technical log markdown into timeline HTML.
|
|
64
|
+
* Falls back to plain markdown rendering if preprocessing fails.
|
|
65
|
+
*
|
|
66
|
+
* @param logMarkdown - The raw markdown content of the technical log
|
|
67
|
+
* @returns HTML string (timeline if successful, plain markdown otherwise)
|
|
68
|
+
*/
|
|
69
|
+
export declare function transformLogToTimeline(logMarkdown: string): Promise<string>;
|