project-logbook 0.3.3 → 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 +54 -76
- package/dist/commands/init.js +11 -11
- package/dist/commands/lint.js +27 -10
- package/dist/commands/list.js +18 -18
- package/dist/commands/log.js +5 -5
- package/dist/commands/new.js +19 -20
- package/dist/commands/preview.js +9 -8
- package/dist/commands/release.js +6 -6
- package/dist/commands/start.js +26 -21
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +138 -0
- package/dist/commands/steer.js +9 -9
- package/dist/commands/upgrade.js +5 -5
- package/dist/index.js +9 -8
- package/dist/lib/build-helpers.js +30 -67
- package/dist/lib/build-steps.d.ts +20 -0
- package/dist/lib/build-steps.js +57 -0
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +27 -3
- package/dist/lib/entry-id.d.ts +22 -0
- package/dist/lib/entry-id.js +26 -0
- package/dist/lib/entry-paths.d.ts +23 -0
- package/dist/lib/entry-paths.js +55 -0
- package/dist/lib/git-helpers.d.ts +32 -1
- package/dist/lib/git-helpers.js +119 -26
- package/dist/lib/hast-helpers.d.ts +10 -0
- package/dist/lib/hast-helpers.js +22 -0
- package/dist/lib/html-attributes.d.ts +17 -0
- package/dist/lib/html-attributes.js +17 -0
- package/dist/lib/html-escape.d.ts +16 -0
- package/dist/lib/html-escape.js +38 -0
- package/dist/lib/image-helpers.js +26 -33
- package/dist/lib/lint-runner.js +5 -5
- package/dist/lib/markdown-processors.d.ts +22 -0
- package/dist/lib/markdown-processors.js +68 -0
- package/dist/lib/package-version.d.ts +5 -0
- package/dist/lib/package-version.js +16 -0
- package/dist/lib/rss.d.ts +29 -0
- package/dist/lib/rss.js +77 -0
- package/dist/lib/styles.js +5 -2
- package/dist/lib/template-helpers.d.ts +4 -3
- package/dist/lib/template-helpers.js +51 -27
- package/dist/lib/template-types.d.ts +2 -2
- package/dist/lib/templates.d.ts +12 -3
- package/dist/lib/templates.js +59 -52
- package/dist/lib/theme.d.ts +37 -0
- package/dist/lib/theme.js +50 -0
- package/dist/lib/url-helpers.d.ts +13 -0
- package/dist/lib/url-helpers.js +27 -0
- package/dist/linters/diff-to-narrative.d.ts +6 -0
- package/dist/linters/diff-to-narrative.js +114 -0
- package/dist/linters/index.js +4 -0
- package/dist/linters/technical-log.d.ts +7 -0
- package/dist/linters/technical-log.js +72 -0
- package/dist/templates/CONTRIBUTING.md +12 -3
- package/dist/templates/index.md +10 -6
- package/dist/templates/log.md +5 -0
- package/dist/templates/logbook-client.js +42 -16
- package/dist/templates/steer.txt +21 -5
- package/dist/templates/styles.css +121 -0
- package/dist/utils/date.d.ts +30 -1
- package/dist/utils/date.js +68 -15
- package/dist/utils/frontmatter.d.ts +26 -0
- package/dist/utils/frontmatter.js +37 -0
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +23 -0
- package/dist/utils/log-timeline.d.ts +69 -0
- package/dist/utils/log-timeline.js +218 -0
- package/package.json +4 -2
- package/src/templates/CONTRIBUTING.md +12 -3
- package/src/templates/index.md +10 -6
- package/src/templates/log.md +5 -0
- package/src/templates/logbook-client.js +42 -16
- package/src/templates/steer.txt +21 -5
- package/src/templates/styles.css +121 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Success message styling (green) */
|
|
2
|
+
export declare const success: import("chalk").ChalkInstance;
|
|
3
|
+
/** Error message styling (red) */
|
|
4
|
+
export declare const error: import("chalk").ChalkInstance;
|
|
5
|
+
/** Warning message styling (yellow) */
|
|
6
|
+
export declare const warning: import("chalk").ChalkInstance;
|
|
7
|
+
/** Info/secondary message styling (blue) */
|
|
8
|
+
export declare const info: import("chalk").ChalkInstance;
|
|
9
|
+
/** Neutral/disabled styling (gray) */
|
|
10
|
+
export declare const neutral: import("chalk").ChalkInstance;
|
|
11
|
+
/** Highlight/emphasis styling (cyan) */
|
|
12
|
+
export declare const highlight: import("chalk").ChalkInstance;
|
|
13
|
+
/** Bold text */
|
|
14
|
+
export declare const bold: import("chalk").ChalkInstance;
|
|
15
|
+
/**
|
|
16
|
+
* Format a section header (e.g., "Checking project integrity...").
|
|
17
|
+
* Uses blue color for visual separation.
|
|
18
|
+
*/
|
|
19
|
+
export declare function header(text: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Format an error message with context.
|
|
22
|
+
* @param prefix - The category/command name
|
|
23
|
+
* @param message - The error message
|
|
24
|
+
*/
|
|
25
|
+
export declare function errorMessage(prefix: string, message: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Format a warning message with context.
|
|
28
|
+
* @param prefix - The category/command name
|
|
29
|
+
* @param message - The warning message
|
|
30
|
+
*/
|
|
31
|
+
export declare function warningMessage(prefix: string, message: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Format a success message with context.
|
|
34
|
+
* @param prefix - The category/command name
|
|
35
|
+
* @param message - The success message
|
|
36
|
+
*/
|
|
37
|
+
export declare function successMessage(prefix: string, message: string): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized styling/theme for CLI output.
|
|
3
|
+
* Ensures consistent colors and visual hierarchy across all commands.
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
/** Success message styling (green) */
|
|
7
|
+
export const success = chalk.green;
|
|
8
|
+
/** Error message styling (red) */
|
|
9
|
+
export const error = chalk.red;
|
|
10
|
+
/** Warning message styling (yellow) */
|
|
11
|
+
export const warning = chalk.yellow;
|
|
12
|
+
/** Info/secondary message styling (blue) */
|
|
13
|
+
export const info = chalk.blue;
|
|
14
|
+
/** Neutral/disabled styling (gray) */
|
|
15
|
+
export const neutral = chalk.gray;
|
|
16
|
+
/** Highlight/emphasis styling (cyan) */
|
|
17
|
+
export const highlight = chalk.cyan;
|
|
18
|
+
/** Bold text */
|
|
19
|
+
export const bold = chalk.bold;
|
|
20
|
+
/**
|
|
21
|
+
* Format a section header (e.g., "Checking project integrity...").
|
|
22
|
+
* Uses blue color for visual separation.
|
|
23
|
+
*/
|
|
24
|
+
export function header(text) {
|
|
25
|
+
return info(text);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Format an error message with context.
|
|
29
|
+
* @param prefix - The category/command name
|
|
30
|
+
* @param message - The error message
|
|
31
|
+
*/
|
|
32
|
+
export function errorMessage(prefix, message) {
|
|
33
|
+
return `${error(prefix)}: ${message}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Format a warning message with context.
|
|
37
|
+
* @param prefix - The category/command name
|
|
38
|
+
* @param message - The warning message
|
|
39
|
+
*/
|
|
40
|
+
export function warningMessage(prefix, message) {
|
|
41
|
+
return `${warning(prefix)}: ${message}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Format a success message with context.
|
|
45
|
+
* @param prefix - The category/command name
|
|
46
|
+
* @param message - The success message
|
|
47
|
+
*/
|
|
48
|
+
export function successMessage(prefix, message) {
|
|
49
|
+
return `${success(prefix)}: ${message}`;
|
|
50
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a URL/path is external (has an absolute URI scheme).
|
|
3
|
+
* @param url - The URL or path to check
|
|
4
|
+
* @returns true if the URL has an absolute URI scheme (http://, https://, ftp://, etc.)
|
|
5
|
+
*/
|
|
6
|
+
export declare function isExternalUrl(url: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Check if a value is an external URL or an anchor link.
|
|
9
|
+
* Used to identify URLs that should not be processed (rewritten, collected, etc.)
|
|
10
|
+
* @param href - The href/src value to check
|
|
11
|
+
* @returns true if the value is external or an anchor
|
|
12
|
+
*/
|
|
13
|
+
export declare function isExternalUrlOrAnchor(href: string): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regex pattern to detect absolute URLs with URI schemes.
|
|
3
|
+
* Matches URIs like: http://, https://, ftp://, data:, etc.
|
|
4
|
+
* Pattern breakdown:
|
|
5
|
+
* ^[a-z] - Starts with a letter (lowercase)
|
|
6
|
+
* [a-z\d+\-.]*: - Followed by alphanumeric, +, -, or . characters, ending with :
|
|
7
|
+
* Example matches: http://, https://, ftp://, mailto:, data:
|
|
8
|
+
* Used to distinguish external URLs from relative paths.
|
|
9
|
+
*/
|
|
10
|
+
const EXTERNAL_URL_SCHEME_REGEX = /^[a-z][a-z\d+\-.]*:/i;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a URL/path is external (has an absolute URI scheme).
|
|
13
|
+
* @param url - The URL or path to check
|
|
14
|
+
* @returns true if the URL has an absolute URI scheme (http://, https://, ftp://, etc.)
|
|
15
|
+
*/
|
|
16
|
+
export function isExternalUrl(url) {
|
|
17
|
+
return EXTERNAL_URL_SCHEME_REGEX.test(url);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check if a value is an external URL or an anchor link.
|
|
21
|
+
* Used to identify URLs that should not be processed (rewritten, collected, etc.)
|
|
22
|
+
* @param href - The href/src value to check
|
|
23
|
+
* @returns true if the value is external or an anchor
|
|
24
|
+
*/
|
|
25
|
+
export function isExternalUrlOrAnchor(href) {
|
|
26
|
+
return EXTERNAL_URL_SCHEME_REGEX.test(href) || href.startsWith('#');
|
|
27
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Linter } from '../lib/lint-types.js';
|
|
2
|
+
export declare function stripFrontmatter(content: string): string;
|
|
3
|
+
export declare function stripMarkdown(text: string): string;
|
|
4
|
+
export declare function getWordCount(content: string): number;
|
|
5
|
+
declare const linter: Linter;
|
|
6
|
+
export default linter;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { getActiveEntry } from '../lib/session.js';
|
|
2
|
+
import { getChangedLOC } from '../lib/git-helpers.js';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
// Thresholds & Limits Configuration
|
|
6
|
+
const TRIVIAL_LOC_MAX = 20;
|
|
7
|
+
const STANDARD_LOC_MAX = 150;
|
|
8
|
+
const TRIVIAL_WORDS = { min: 30, max: 150 };
|
|
9
|
+
const STANDARD_WORDS = { min: 50, max: 300 };
|
|
10
|
+
const LARGE_WORDS = { min: 100, max: 400 };
|
|
11
|
+
export function stripFrontmatter(content) {
|
|
12
|
+
const lines = content.split(/\r?\n/);
|
|
13
|
+
if (lines[0]?.trim() === '---') {
|
|
14
|
+
const endIdx = lines.indexOf('---', 1);
|
|
15
|
+
if (endIdx !== -1) {
|
|
16
|
+
return lines.slice(endIdx + 1).join('\n');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return content;
|
|
20
|
+
}
|
|
21
|
+
export function stripMarkdown(text) {
|
|
22
|
+
return text
|
|
23
|
+
.replace(/<[^>]*>/g, '') // HTML tags
|
|
24
|
+
.replace(/!\[([^\]]*)]\([^)]*\)/g, '$1') // images
|
|
25
|
+
.replace(/\[([^\]]*)]\([^)]*\)/g, '$1') // links
|
|
26
|
+
.replace(/```[\s\S]*?```/g, '') // code blocks
|
|
27
|
+
.replace(/`([^`]+)`/g, '$1') // inline code
|
|
28
|
+
.replace(/(\*\*|__)(.*?)\1/g, '$2') // bold
|
|
29
|
+
.replace(/(\*|_)(.*?)\1/g, '$2') // italic
|
|
30
|
+
.replace(/^\s*>\s+/gm, '') // blockquotes
|
|
31
|
+
.replace(/^\s*#+\s+/gm, '') // headings
|
|
32
|
+
.replace(/^\s*[-*+]\s+/gm, '') // list bullets
|
|
33
|
+
.replace(/^\s*\d+\.\s+/gm, ''); // ordered lists
|
|
34
|
+
}
|
|
35
|
+
export function getWordCount(content) {
|
|
36
|
+
const clean = stripMarkdown(stripFrontmatter(content));
|
|
37
|
+
return clean.split(/\s+/).filter((w) => w.trim().length > 0).length;
|
|
38
|
+
}
|
|
39
|
+
const linter = {
|
|
40
|
+
name: 'diff-to-narrative',
|
|
41
|
+
description: 'Checks word count of active index.md against total LOC changed',
|
|
42
|
+
async check(context) {
|
|
43
|
+
// Only target project-level checks
|
|
44
|
+
if (context.entryName)
|
|
45
|
+
return [];
|
|
46
|
+
const active = await getActiveEntry();
|
|
47
|
+
if (!active || active.source !== 'lockfile') {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const logbookDir = context.config.logbookDir;
|
|
51
|
+
const { total: totalLOC } = await getChangedLOC(logbookDir);
|
|
52
|
+
// If there are no active changes and the tree is completely clean, skip gracefully
|
|
53
|
+
if (totalLOC === 0) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const indexPath = join(process.cwd(), logbookDir, active.slug, 'index.md');
|
|
57
|
+
if (!(await fs.pathExists(indexPath))) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
const content = await fs.readFile(indexPath, 'utf-8');
|
|
61
|
+
const wordCount = getWordCount(content);
|
|
62
|
+
const issues = [];
|
|
63
|
+
if (totalLOC < TRIVIAL_LOC_MAX) {
|
|
64
|
+
if (wordCount < TRIVIAL_WORDS.min) {
|
|
65
|
+
issues.push({
|
|
66
|
+
level: 'warning',
|
|
67
|
+
category: 'story-ratio',
|
|
68
|
+
message: `Code change is trivial (${totalLOC} LOC), but index.md has only ${wordCount} words (minimum is ${TRIVIAL_WORDS.min}). Please expand the narrative.`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else if (wordCount > TRIVIAL_WORDS.max) {
|
|
72
|
+
issues.push({
|
|
73
|
+
level: 'warning',
|
|
74
|
+
category: 'story-ratio',
|
|
75
|
+
message: `Warning: Code change is trivial, but index.md exceeds ${TRIVIAL_WORDS.max} words. Please summarize.`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (totalLOC <= STANDARD_LOC_MAX) {
|
|
80
|
+
if (wordCount < STANDARD_WORDS.min) {
|
|
81
|
+
issues.push({
|
|
82
|
+
level: 'warning',
|
|
83
|
+
category: 'story-ratio',
|
|
84
|
+
message: `Code change is standard (${totalLOC} LOC), but index.md has only ${wordCount} words (minimum is ${STANDARD_WORDS.min}). Please expand the narrative.`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else if (wordCount > STANDARD_WORDS.max) {
|
|
88
|
+
issues.push({
|
|
89
|
+
level: 'warning',
|
|
90
|
+
category: 'story-ratio',
|
|
91
|
+
message: `Warning: Code change is standard (${totalLOC} LOC), but index.md exceeds ${STANDARD_WORDS.max} words. Please summarize.`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
if (wordCount < LARGE_WORDS.min) {
|
|
97
|
+
issues.push({
|
|
98
|
+
level: 'warning',
|
|
99
|
+
category: 'story-ratio',
|
|
100
|
+
message: `Code change is large (${totalLOC} LOC), but index.md has only ${wordCount} words (minimum is ${LARGE_WORDS.min}). Please expand the narrative.`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else if (wordCount > LARGE_WORDS.max) {
|
|
104
|
+
issues.push({
|
|
105
|
+
level: 'warning',
|
|
106
|
+
category: 'story-ratio',
|
|
107
|
+
message: `Warning: Story exceeds ${LARGE_WORDS.max} words. Consider summarizing and linking to a Wiki/ADR.`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return issues;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
export default linter;
|
package/dist/linters/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import diffToNarrative from './diff-to-narrative.js';
|
|
1
2
|
import frontmatter from './frontmatter.js';
|
|
2
3
|
import jiraPrefix from './jira-prefix.js';
|
|
3
4
|
import links from './links.js';
|
|
@@ -5,8 +6,10 @@ import lockfile from './lockfile.js';
|
|
|
5
6
|
import placeholders from './placeholders.js';
|
|
6
7
|
import projectIntegrity from './project-integrity.js';
|
|
7
8
|
import readability from './readability.js';
|
|
9
|
+
import technicalLog from './technical-log.js';
|
|
8
10
|
import workspaces from './workspaces.js';
|
|
9
11
|
export const linters = [
|
|
12
|
+
diffToNarrative,
|
|
10
13
|
frontmatter,
|
|
11
14
|
jiraPrefix,
|
|
12
15
|
links,
|
|
@@ -14,5 +17,6 @@ export const linters = [
|
|
|
14
17
|
placeholders,
|
|
15
18
|
projectIntegrity,
|
|
16
19
|
readability,
|
|
20
|
+
technicalLog,
|
|
17
21
|
workspaces,
|
|
18
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;
|
|
@@ -47,12 +47,21 @@ Only update the currently active logbook entry. Do not edit other existing entri
|
|
|
47
47
|
When you finish writing `index.md`, **remove the boilerplate link line** that the template inserts:
|
|
48
48
|
|
|
49
49
|
### 3. Quality Assurance
|
|
50
|
-
Before
|
|
50
|
+
Before finalizing your work, you should run the linter to verify there are no errors:
|
|
51
51
|
```bash
|
|
52
|
-
logbook release
|
|
53
52
|
logbook lint
|
|
54
53
|
```
|
|
55
|
-
|
|
54
|
+
|
|
55
|
+
You can also check the overall status of the logbook, configuration settings, and statistics (total tasks, done, and drafts) using:
|
|
56
|
+
```bash
|
|
57
|
+
logbook status
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The linter will warn you if the lockfile `.logbook-active` is still present, which is expected during active development.
|
|
61
|
+
|
|
62
|
+
Once the work is ready:
|
|
63
|
+
- **If you are an AI agent**: Do not release the entry. Hand back to the prompter, who will review and run `logbook release`.
|
|
64
|
+
- **If you are a human developer**: Run `logbook release` to remove the lockfile and finalize the entry.
|
|
56
65
|
|
|
57
66
|
The linter enforces rules to be followed.
|
|
58
67
|
|
package/dist/templates/index.md
CHANGED
|
@@ -3,24 +3,28 @@
|
|
|
3
3
|
ticket: {{id}}
|
|
4
4
|
# The title should be a human-readable description of the work
|
|
5
5
|
title: {{title}}
|
|
6
|
-
prompter: [PROMPTER]
|
|
7
|
-
harness: [HARNESS]
|
|
8
|
-
llm: [LLM]
|
|
9
|
-
summary: [WRITE_SUMMARY_HERE]
|
|
6
|
+
prompter: "[PROMPTER]"
|
|
7
|
+
harness: "[HARNESS]"
|
|
8
|
+
llm: "[LLM]"
|
|
9
|
+
summary: "[WRITE_SUMMARY_HERE]"
|
|
10
10
|
# Tags for categorizing the change (must be from allowed list in .project-logbook)
|
|
11
11
|
tags: []
|
|
12
12
|
# Workspace(s) this change affects (must match package.json workspaces; leave empty for single-project)
|
|
13
13
|
# Example: workspaces: ["frontend", "shared"]
|
|
14
14
|
workspaces: []
|
|
15
15
|
# Set automatically by `logbook start`
|
|
16
|
-
dateStart: [DATE_START]
|
|
16
|
+
dateStart: "[DATE_START]"
|
|
17
17
|
# Set automatically by `logbook release`
|
|
18
|
-
dateEnd: [DATE_END]
|
|
18
|
+
dateEnd: "[DATE_END]"
|
|
19
19
|
---
|
|
20
20
|
|
|
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.
|
|
@@ -2,27 +2,40 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/* ── 1. Real-time relative dates ──────────────────────────────────────── */
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Calculate the relative date string (today, yesterday, 2 days ago, etc)
|
|
7
|
+
* Prepend the relative component to the locally-formatted absolute date.
|
|
8
|
+
*/
|
|
9
|
+
function getRelativeComponent(d) {
|
|
7
10
|
var now = new Date();
|
|
8
11
|
// Normalise both to midnight local time for day-diff calculation
|
|
9
12
|
var dDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
10
13
|
var nDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
11
14
|
var diffDays = Math.round((nDay - dDay) / 86400000);
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
if (diffDays ===
|
|
15
|
-
else if (diffDays
|
|
16
|
-
else if (diffDays <
|
|
17
|
-
else if (diffDays <
|
|
18
|
-
else if (diffDays <
|
|
19
|
-
else if (diffDays <
|
|
20
|
-
else if (diffDays <
|
|
21
|
-
else
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
if (diffDays === 0) return 'today';
|
|
17
|
+
else if (diffDays === 1) return 'yesterday';
|
|
18
|
+
else if (diffDays < 7) return diffDays + ' days ago';
|
|
19
|
+
else if (diffDays < 14) return '1 week ago';
|
|
20
|
+
else if (diffDays < 30) return Math.floor(diffDays / 7) + ' weeks ago';
|
|
21
|
+
else if (diffDays < 60) return '1 month ago';
|
|
22
|
+
else if (diffDays < 365) return Math.floor(diffDays / 30) + ' months ago';
|
|
23
|
+
else if (diffDays < 730) return '1 year ago';
|
|
24
|
+
else return Math.floor(diffDays / 365) + ' years ago';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format a Date object as absolute date and time in browser's local timezone.
|
|
29
|
+
* Format: "26 May 2026, 10:51"
|
|
30
|
+
*/
|
|
31
|
+
function formatAbsoluteComponent(d) {
|
|
32
|
+
var day = d.getDate();
|
|
33
|
+
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
34
|
+
var month = months[d.getMonth()];
|
|
35
|
+
var year = d.getFullYear();
|
|
36
|
+
var hours = d.getHours() < 10 ? '0' + d.getHours() : d.getHours();
|
|
37
|
+
var minutes = d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes();
|
|
38
|
+
return day + ' ' + month + ' ' + year + ', ' + hours + ':' + minutes;
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
function updateRelativeDates() {
|
|
@@ -30,7 +43,20 @@
|
|
|
30
43
|
for (var i = 0; i < els.length; i++) {
|
|
31
44
|
var el = els[i];
|
|
32
45
|
var iso = el.getAttribute('data-date');
|
|
33
|
-
if (iso)
|
|
46
|
+
if (iso) {
|
|
47
|
+
var d = new Date(iso);
|
|
48
|
+
if (!isNaN(d.getTime())) {
|
|
49
|
+
// Format the absolute date in the user's browser/local timezone
|
|
50
|
+
var absolute = formatAbsoluteComponent(d);
|
|
51
|
+
// Prepend the relative component (e.g., "today")
|
|
52
|
+
var relative = getRelativeComponent(d);
|
|
53
|
+
if (relative) {
|
|
54
|
+
el.textContent = relative + ', ' + absolute;
|
|
55
|
+
} else {
|
|
56
|
+
el.textContent = absolute;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
34
60
|
}
|
|
35
61
|
}
|
|
36
62
|
|
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`.
|
|
@@ -33,6 +33,11 @@ header h1 {
|
|
|
33
33
|
header h1 a {
|
|
34
34
|
text-decoration: none !important;
|
|
35
35
|
color: black !important;
|
|
36
|
+
transition: color 0.2s;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
header h1 a:hover {
|
|
40
|
+
color: var(--primary) !important;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
.tagline {
|
|
@@ -691,3 +696,119 @@ article img[src$='.webp'],
|
|
|
691
696
|
article img[src$='.svg'] {
|
|
692
697
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
693
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
|
+
}
|