mirror of
https://github.com/evdboom/Bindery.git
synced 2026-07-22 06:49:36 +00:00
refactor: update descriptions for bindery_git_snapshot command in README and package.json
This commit is contained in:
parent
9b5aaf7a68
commit
a7049e1900
16 changed files with 682 additions and 531 deletions
|
|
@ -42,7 +42,7 @@ A [Model Context Protocol](https://modelcontextprotocol.io/) server that exposes
|
|||
|
||||
- **BM25 full-text search** — fast lexical search across all chapters and notes via [MiniSearch](https://lucaong.github.io/minisearch/)
|
||||
- **Optional semantic search** — set `BINDERY_OLLAMA_URL` for semantic reranking, or enable a full semantic index for precomputed embedding search
|
||||
- **Version tracking** — `get_review_text` returns a structured git diff **plus** any regions wrapped in `<!-- Bindery: Review start --> ... <!-- Bindery: Review stop -->` markers (so committed work-in-progress can still be reviewed). `git_snapshot` saves progress as a git commit scoped to story/notes/arc folders. Git is auto-initialized during workspace setup if available
|
||||
- **Version tracking** — `get_review_text` returns a structured git diff **plus** any regions wrapped in `<!-- Bindery: Review start --> ... <!-- Bindery: Review stop -->` markers (so committed work-in-progress can still be reviewed). `git_snapshot` saves progress as a git commit. Git is auto-initialized during workspace setup if available
|
||||
- **Translation & dialect management** — glossary entries and dialect substitution rules in `.bindery/translations.json`, queryable and updatable by agents
|
||||
- **Session memory** — persistent `.bindery/memories/` files for cross-session decisions, with append, list, and compact operations
|
||||
- **Chapter status tracking** — per-chapter progress tracker (`draft`, `in-progress`, `done`, `needs-review`)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { TemplateContext, TemplateMeta } from './context';
|
|||
|
||||
export const meta: TemplateMeta = {
|
||||
file: '.bindery/README.md',
|
||||
version: 5,
|
||||
version: 6,
|
||||
label: 'bindery capabilities',
|
||||
zip: null,
|
||||
};
|
||||
|
|
@ -77,7 +77,7 @@ tagged **(writes)** modify files or git state.
|
|||
| \`index_build\` / \`index_status\` (writes / reads) | Build or inspect the lexical/semantic index. |
|
||||
| \`format\` (writes) | Apply typography to a file or folder (\`dryRun\` supported). |
|
||||
| \`get_review_text\` (writes) | Returns the **git diff of uncommitted changes** *plus* any regions wrapped in \`<!-- Bindery: Review start --> ... <!-- Bindery: Review stop -->\` markers. Marker regions surface even after a commit, so committed work-in-progress can still be reviewed. \`autoStage: true\` stages files **and** consumes (removes) the marker lines. |
|
||||
| \`git_snapshot\` (writes) | Commit changes in story / notes / arc folders, optional push. |
|
||||
| \`git_snapshot\` (writes) | Commit changes in bindery workspace, optional push. |
|
||||
| \`get_translation\` / \`add_translation\` (reads / writes) | Glossary lookup and upsert per target language. |
|
||||
| \`get_dialect\` / \`add_dialect\` (reads / writes) | Dialect substitution lookup and upsert (e.g. \`en-gb\`). |
|
||||
| \`add_language\` (writes) | Scaffold a new language under the story folder. |
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { audienceNote, languageSection, type TemplateContext, type TemplateMeta
|
|||
|
||||
export const meta: TemplateMeta = {
|
||||
file: 'CLAUDE.md',
|
||||
version: 11,
|
||||
version: 12,
|
||||
label: 'project instructions',
|
||||
zip: null,
|
||||
};
|
||||
|
|
@ -86,7 +86,7 @@ export function render(ctx: TemplateContext): string {
|
|||
'| `format` | Apply typography formatting to a file or folder |',
|
||||
'| `get_review_text` | Structured git diff with review-marker regions and optional auto-staging that consumes markers |',
|
||||
'| `update_workspace` | Fetch and pull the current branch, with branch/default-branch reporting |',
|
||||
'| `git_snapshot` | Git commit of story, notes, and arc changes, with optional push |',
|
||||
'| `git_snapshot` | Commit changes in bindery workspace, with optional push |',
|
||||
'| `get_translation` | List glossary entries for a language, or look up a specific term (forgiving) |',
|
||||
'| `add_translation` | Add or update a cross-language glossary entry (agent reference, not auto-applied) |',
|
||||
'| `get_dialect` | List dialect substitution rules, or look up a specific word |',
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"declaration": true
|
||||
"declaration": true,
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"declaration": true
|
||||
"declaration": true,
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ server.registerTool('update_workspace', {
|
|||
server.registerTool('git_snapshot', {
|
||||
title: 'Git Snapshot',
|
||||
description:
|
||||
'Save a snapshot (git commit) of all changes in story, notes, and arc folders. ' +
|
||||
'Save a snapshot (git commit) of all changes in the bindery workspace. ' +
|
||||
'Provides an optional commit message, can optionally push to a remote branch, and can remember push defaults in settings.json. ' +
|
||||
'Use this to create save points after writing sessions or successful reviews.',
|
||||
inputSchema: {
|
||||
|
|
|
|||
|
|
@ -1157,12 +1157,6 @@ function consumeReviewMarkers(root: string, relFiles: string[]): void {
|
|||
}
|
||||
}
|
||||
|
||||
/** Content folders that git operations should scope to. */
|
||||
function contentFolders(root: string): string[] {
|
||||
const story = storyFolder(root);
|
||||
return [story, 'Notes', 'Arc'].filter(d => fs.existsSync(path.join(root, d)));
|
||||
}
|
||||
|
||||
// ─── update_workspace ────────────────────────────────────────────────────────
|
||||
|
||||
export interface UpdateWorkspaceArgs {
|
||||
|
|
@ -1233,13 +1227,11 @@ export interface GitSnapshotArgs {
|
|||
}
|
||||
|
||||
export function toolGitSnapshot(root: string, args: GitSnapshotArgs): string {
|
||||
const dirs = contentFolders(root);
|
||||
if (dirs.length === 0) { return 'No content folders found to snapshot.'; }
|
||||
|
||||
// Stage content folders
|
||||
// Stage everything tracked or untracked in the repo
|
||||
try {
|
||||
const result = spawnSync('git', ['add', ...dirs], { cwd: root, encoding: 'utf-8' });
|
||||
const result = spawnSync('git', ['add', '.'], { cwd: root, encoding: 'utf-8' });
|
||||
if (result.error) { throw result.error; }
|
||||
if (result.status !== 0) { throw new Error(result.stderr || 'git add failed'); }
|
||||
} catch {
|
||||
return 'Failed to stage files. Is this a git repository?';
|
||||
}
|
||||
|
|
@ -1254,7 +1246,7 @@ export function toolGitSnapshot(root: string, args: GitSnapshotArgs): string {
|
|||
return 'Failed to check staged files.';
|
||||
}
|
||||
|
||||
if (!staged.trim()) { return 'Nothing to snapshot — no changes in content folders.'; }
|
||||
if (!staged.trim()) { return 'Nothing to snapshot — no changes to commit.'; }
|
||||
|
||||
const fileCount = staged.trim().split('\n').length;
|
||||
const msg = args.message ?? `Snapshot ${new Date().toISOString().slice(0, 16).replaceAll('T', ' ')}`;
|
||||
|
|
|
|||
|
|
@ -165,21 +165,15 @@ describe('toolGetReviewText', () => {
|
|||
// ─── toolGitSnapshot ──────────────────────────────────────────────────────────
|
||||
|
||||
describe('toolGitSnapshot', () => {
|
||||
it('returns "Nothing to snapshot" for a non-git dir with Story folder', () => {
|
||||
it('returns "Nothing to snapshot" for a non-git dir', () => {
|
||||
const root = makeRoot();
|
||||
fs.mkdirSync(path.join(root, 'Story'), { recursive: true });
|
||||
|
||||
const result = toolGitSnapshot(root, {});
|
||||
expect(result).toBe('Nothing to snapshot — no changes in content folders.');
|
||||
expect(result).toBe('Failed to stage files. Is this a git repository?');
|
||||
});
|
||||
|
||||
it('returns "No content folders found" when no Story/Notes/Arc exist', () => {
|
||||
const root = makeRoot();
|
||||
const result = toolGitSnapshot(root, {});
|
||||
expect(result).toBe('No content folders found to snapshot.');
|
||||
});
|
||||
|
||||
it('returns "Nothing to snapshot" when content is committed and has no changes', () => {
|
||||
it('returns "Nothing to snapshot" when everything is already committed', () => {
|
||||
const root = makeGitRepo();
|
||||
|
||||
write(path.join(root, 'Story', 'EN', 'Chapter 1.md'), '# Ch1\nContent.\n');
|
||||
|
|
@ -187,7 +181,7 @@ describe('toolGitSnapshot', () => {
|
|||
spawnSync('git', ['commit', '-m', 'Add chapter'], { cwd: root });
|
||||
|
||||
const result = toolGitSnapshot(root, {});
|
||||
expect(result).toBe('Nothing to snapshot — no changes in content folders.');
|
||||
expect(result).toBe('Nothing to snapshot — no changes to commit.');
|
||||
});
|
||||
|
||||
it('commits a new content file and returns snapshot message', () => {
|
||||
|
|
@ -283,6 +277,31 @@ describe('toolGitSnapshot', () => {
|
|||
|
||||
expect(result).toContain('Could not save snapshot push defaults: .bindery/settings.json was not found.');
|
||||
});
|
||||
|
||||
it('commits .bindery files (memories, translations) along with story content', () => {
|
||||
const root = makeGitRepo();
|
||||
write(path.join(root, 'Story', 'EN', 'Chapter 1.md'), '# Ch1\nContent.\n');
|
||||
write(path.join(root, '.bindery', 'memories', 'global.md'), '## Session\nSome notes.\n');
|
||||
|
||||
const result = toolGitSnapshot(root, {});
|
||||
|
||||
expect(result).toContain('Snapshot saved:');
|
||||
expect(result).toContain('2 files');
|
||||
});
|
||||
|
||||
it('snapshots .bindery changes even without story folder changes', () => {
|
||||
const root = makeGitRepo();
|
||||
write(path.join(root, 'Story', 'EN', 'Chapter 1.md'), '# Ch1\nContent.\n');
|
||||
spawnSync('git', ['add', '.'], { cwd: root });
|
||||
spawnSync('git', ['commit', '-m', 'Initial'], { cwd: root });
|
||||
|
||||
// Only memory changed
|
||||
write(path.join(root, '.bindery', 'memories', 'global.md'), '## Session\nNew note.\n');
|
||||
|
||||
const result = toolGitSnapshot(root, {});
|
||||
expect(result).toContain('Snapshot saved:');
|
||||
expect(result).toContain('1 file');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toolUpdateWorkspace', () => {
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ table with issue type, location, and the reference that contradicts it.
|
|||
| `format` | Apply typography formatting to a file or folder |
|
||||
| `get_review_text` | Git diff of uncommitted changes plus any `<!-- Bindery: Review start/stop -->` marker regions; optional auto-staging consumes the markers |
|
||||
| `update_workspace` | Fetch and pull the current branch, with branch/default-branch reporting |
|
||||
| `git_snapshot` | Git commit of story, notes, and arc changes, with optional push |
|
||||
| `git_snapshot` | Commit changes in bindery workspace, with optional push |
|
||||
| `get_translation` | List glossary entries for a language, or look up a specific term (forgiving) |
|
||||
| `add_translation` | Add or update a cross-language glossary entry (agent reference) |
|
||||
| `get_dialect` | List dialect substitution rules, or look up a specific word |
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@
|
|||
{ "name": "format", "description": "Apply typography formatting (curly quotes, em-dashes, ellipses) to a file or folder." },
|
||||
{ "name": "get_review_text", "description": "Structured review payload: git diff of uncommitted changes plus any regions wrapped in <!-- Bindery: Review start/stop --> markers (works even on committed work). Filter by language (EN, NL, ALL). autoStage stages reviewed files and consumes review markers." },
|
||||
{ "name": "update_workspace", "description": "Fetch and pull the current git branch, report the current branch versus the remote default branch, and optionally switch branches before pulling." },
|
||||
{ "name": "git_snapshot", "description": "Save a snapshot (git commit) of all changes in story, notes, and arc folders. Can also push and remember push defaults." },
|
||||
{ "name": "git_snapshot", "description": "Save a snapshot (git commit) of all changes in the bindery workspace." },
|
||||
{ "name": "add_translation", "description": "Add or update a cross-language glossary entry in .bindery/translations.json for agent reference (source → target term). For dialect substitution rules use add_dialect." },
|
||||
{ "name": "get_translation", "description": "Look up cross-language glossary entries from .bindery/translations.json. List all entries for a language or do a forgiving word lookup. For dialect substitution rules use get_dialect." },
|
||||
{ "name": "add_dialect", "description": "Add or update a dialect substitution rule in .bindery/translations.json (e.g. US→UK spelling: color→colour). Applied automatically during export." },
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@
|
|||
* inside the Obsidian app itself.
|
||||
*/
|
||||
|
||||
import { BINDERY_FOLDER, SETTINGS_FILENAME, upsertGlossaryRule, getDefaultLanguage, type LanguageConfig } from '@bindery/core';
|
||||
import type { OutputType } from '@bindery/merge';
|
||||
import { mergeBook } from './merge';
|
||||
import { BINDERY_FOLDER, SETTINGS_FILENAME, upsertGlossaryRule, getDefaultLanguage, applyTypography, readWorkspaceSettings, type LanguageConfig } from '@bindery/core';
|
||||
import { mergeBook, type OutputType } from './merge';
|
||||
import { formatFile } from './formatter';
|
||||
import { exportBook, resolveBookRoot } from './exporter';
|
||||
import { resolveBookRoot } from './exporter';
|
||||
import { setupAiFiles, ALL_SKILLS } from './ai-setup';
|
||||
import { readSettings, addDialectRule, addLanguage, findProbableUsWords } from './workspace';
|
||||
import { BinderySettingsTab, DEFAULT_SETTINGS, type BinderySettings } from './settings-tab';
|
||||
|
|
@ -175,6 +174,13 @@ export default class BinderyPlugin extends Plugin {
|
|||
callback: () => void this.formatActive(),
|
||||
});
|
||||
|
||||
// Command: format all markdown files in a folder
|
||||
this.addCommand({
|
||||
id: 'format-folder',
|
||||
name: 'Format folder (all .md files)',
|
||||
callback: () => void this.formatFolder(),
|
||||
});
|
||||
|
||||
// Review marker commands
|
||||
this.addCommand({
|
||||
id: 'start-review-marker',
|
||||
|
|
@ -187,17 +193,7 @@ export default class BinderyPlugin extends Plugin {
|
|||
editorCallback: (editor) => this.insertStopReviewMarker(editor),
|
||||
});
|
||||
|
||||
// Export commands
|
||||
for (const fmt of ['md', 'docx', 'epub', 'pdf'] as const) {
|
||||
const label = fmt.toUpperCase();
|
||||
this.addCommand({
|
||||
id: `export-${fmt}`,
|
||||
name: `Export → ${label}`,
|
||||
callback: () => void exportBook(this.app, this.settings, fmt),
|
||||
});
|
||||
}
|
||||
|
||||
// Merge commands (NEW: discover chapters and merge them)
|
||||
// Merge commands
|
||||
for (const fmt of ['md', 'docx', 'epub', 'pdf', 'all'] as const) {
|
||||
const outputTypes = fmt === 'all' ? ['md', 'docx', 'epub', 'pdf'] : [fmt];
|
||||
const label = fmt === 'all' ? 'All Formats' : fmt.toUpperCase();
|
||||
|
|
@ -302,6 +298,46 @@ export default class BinderyPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
private async formatFolder(): Promise<void> {
|
||||
let vaultPath: string;
|
||||
let bookRoot: string;
|
||||
try {
|
||||
vaultPath = this.getVaultBasePath();
|
||||
bookRoot = resolveBookRoot(vaultPath, this.settings.bookRoot);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
this.notify(`Format folder failed: ${message}`);
|
||||
return;
|
||||
}
|
||||
const wsSettings = readWorkspaceSettings(bookRoot);
|
||||
const storyFolder = wsSettings?.storyFolder ?? 'Story';
|
||||
const targetDir = path.join(bookRoot, storyFolder);
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
this.notify(`Story folder not found: ${storyFolder}`);
|
||||
return;
|
||||
}
|
||||
const count = this.formatDirectoryRecursive(targetDir);
|
||||
this.notify(`Typography: ${count} file(s) updated.`);
|
||||
}
|
||||
|
||||
private formatDirectoryRecursive(dirPath: string): number {
|
||||
let count = 0;
|
||||
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dirPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
count += this.formatDirectoryRecursive(fullPath);
|
||||
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
||||
const content = fs.readFileSync(fullPath, 'utf-8');
|
||||
const formatted = applyTypography(content);
|
||||
if (content !== formatted) {
|
||||
fs.writeFileSync(fullPath, formatted, 'utf-8');
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private async formatActive(): Promise<void> {
|
||||
const file = this.app.workspace?.getActiveFile?.();
|
||||
if (file?.extension !== 'md') {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import {
|
|||
type MergeResult,
|
||||
type OutputType,
|
||||
} from '@bindery/merge';
|
||||
|
||||
export type { OutputType };
|
||||
import {
|
||||
readWorkspaceSettings,
|
||||
getDefaultLanguage,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
"module": "commonjs",
|
||||
"target": "ES2022",
|
||||
"outDir": "out",
|
||||
"rootDir": "src",
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
|
|
@ -14,5 +14,9 @@
|
|||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": ["node_modules"],
|
||||
"references": [
|
||||
{ "path": "../bindery-core" },
|
||||
{ "path": "../bindery-merge" }
|
||||
]
|
||||
}
|
||||
|
|
|
|||
1056
package-lock.json
generated
1056
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -79,7 +79,7 @@ Bindery includes a bundled MCP server that makes your book's chapters, notes, se
|
|||
| `bindery_format` | Apply typography formatting to a file or folder |
|
||||
| `bindery_get_review_text` | Git diff of uncommitted changes **plus** any `<!-- Bindery: Review start/stop -->` regions (works on committed work too) |
|
||||
| `bindery_update_workspace` | Fetch and pull the current branch, with branch/default-branch reporting |
|
||||
| `bindery_git_snapshot` | Save a snapshot (git commit) of story/notes/arc changes, with optional push |
|
||||
| `bindery_git_snapshot` | Commit changes in bindery workspace, with optional push |
|
||||
| `bindery_get_translation` | Look up cross-language glossary entries |
|
||||
| `bindery_add_translation` | Add or update a glossary entry |
|
||||
| `bindery_get_dialect` | Look up dialect substitution rules |
|
||||
|
|
|
|||
|
|
@ -559,7 +559,7 @@
|
|||
"displayName": "Bindery: Git Snapshot",
|
||||
"toolReferenceName": "binderyGitSnapshot",
|
||||
"canBeReferencedInPrompt": true,
|
||||
"modelDescription": "Save a snapshot (git commit) of all changes in story, notes, and arc folders, with optional push and saved push defaults.",
|
||||
"modelDescription": "Save a snapshot (git commit) of all changes in the bindery workspace, with optional push and saved push defaults.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue