@bookedsolid/reagent 0.14.0 → 0.14.2

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.
Files changed (58) hide show
  1. package/README.md +147 -1
  2. package/dist/cli/commands/init/index.d.ts.map +1 -1
  3. package/dist/cli/commands/init/index.js +8 -1
  4. package/dist/cli/commands/init/index.js.map +1 -1
  5. package/dist/cli/commands/init/mcp-config.js +6 -6
  6. package/dist/cli/commands/init/mcp-config.js.map +1 -1
  7. package/dist/cli/commands/init/obsidian.d.ts +16 -0
  8. package/dist/cli/commands/init/obsidian.d.ts.map +1 -0
  9. package/dist/cli/commands/init/obsidian.js +95 -0
  10. package/dist/cli/commands/init/obsidian.js.map +1 -0
  11. package/dist/cli/commands/init/policy.d.ts.map +1 -1
  12. package/dist/cli/commands/init/policy.js +10 -1
  13. package/dist/cli/commands/init/policy.js.map +1 -1
  14. package/dist/cli/commands/obsidian.d.ts +2 -0
  15. package/dist/cli/commands/obsidian.d.ts.map +1 -0
  16. package/dist/cli/commands/obsidian.js +187 -0
  17. package/dist/cli/commands/obsidian.js.map +1 -0
  18. package/dist/cli/commands/upgrade-policy.d.ts +17 -0
  19. package/dist/cli/commands/upgrade-policy.d.ts.map +1 -0
  20. package/dist/cli/commands/upgrade-policy.js +123 -0
  21. package/dist/cli/commands/upgrade-policy.js.map +1 -0
  22. package/dist/cli/commands/upgrade.d.ts.map +1 -1
  23. package/dist/cli/commands/upgrade.js +8 -26
  24. package/dist/cli/commands/upgrade.js.map +1 -1
  25. package/dist/cli/index.js +9 -0
  26. package/dist/cli/index.js.map +1 -1
  27. package/dist/gateway/native-tools.d.ts.map +1 -1
  28. package/dist/gateway/native-tools.js +39 -0
  29. package/dist/gateway/native-tools.js.map +1 -1
  30. package/dist/obsidian/cli.d.ts +49 -0
  31. package/dist/obsidian/cli.d.ts.map +1 -0
  32. package/dist/obsidian/cli.js +128 -0
  33. package/dist/obsidian/cli.js.map +1 -0
  34. package/dist/obsidian/index.d.ts +7 -0
  35. package/dist/obsidian/index.d.ts.map +1 -0
  36. package/dist/obsidian/index.js +5 -0
  37. package/dist/obsidian/index.js.map +1 -0
  38. package/dist/obsidian/kanban-generator.d.ts +9 -0
  39. package/dist/obsidian/kanban-generator.d.ts.map +1 -0
  40. package/dist/obsidian/kanban-generator.js +63 -0
  41. package/dist/obsidian/kanban-generator.js.map +1 -0
  42. package/dist/obsidian/vault-config.d.ts +142 -0
  43. package/dist/obsidian/vault-config.d.ts.map +1 -0
  44. package/dist/obsidian/vault-config.js +92 -0
  45. package/dist/obsidian/vault-config.js.map +1 -0
  46. package/dist/obsidian/vault-writer.d.ts +52 -0
  47. package/dist/obsidian/vault-writer.d.ts.map +1 -0
  48. package/dist/obsidian/vault-writer.js +166 -0
  49. package/dist/obsidian/vault-writer.js.map +1 -0
  50. package/dist/types/policy.d.ts +4 -0
  51. package/dist/types/policy.d.ts.map +1 -1
  52. package/hooks/dangerous-bash-interceptor.sh +43 -0
  53. package/hooks/reagent-obsidian-journal.sh +69 -0
  54. package/hooks/reagent-obsidian-precompact.sh +88 -0
  55. package/hooks/reagent-obsidian-tasks.sh +126 -0
  56. package/package.json +1 -1
  57. package/profiles/bst-internal.json +1 -1
  58. package/profiles/client-engagement.json +1 -1
@@ -0,0 +1,9 @@
1
+ import type { TaskView } from '../pm/types.js';
2
+ /**
3
+ * Pure function: converts an array of TaskView into Obsidian Kanban markdown.
4
+ *
5
+ * Output is compatible with the Obsidian Kanban plugin (kanban-plugin: basic frontmatter).
6
+ * Uses reagent_managed frontmatter to identify auto-generated files.
7
+ */
8
+ export declare function generateKanban(tasks: TaskView[]): string;
9
+ //# sourceMappingURL=kanban-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kanban-generator.d.ts","sourceRoot":"","sources":["../../src/obsidian/kanban-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAe/C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAwDxD"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Status-to-column mapping for the Obsidian Kanban plugin.
3
+ */
4
+ const COLUMN_ORDER = ['Backlog', 'In Progress', 'Blocked', 'Done', 'Cancelled'];
5
+ const STATUS_TO_COLUMN = {
6
+ created: 'Backlog',
7
+ started: 'In Progress',
8
+ blocked: 'Blocked',
9
+ completed: 'Done',
10
+ cancelled: 'Cancelled',
11
+ };
12
+ /**
13
+ * Pure function: converts an array of TaskView into Obsidian Kanban markdown.
14
+ *
15
+ * Output is compatible with the Obsidian Kanban plugin (kanban-plugin: basic frontmatter).
16
+ * Uses reagent_managed frontmatter to identify auto-generated files.
17
+ */
18
+ export function generateKanban(tasks) {
19
+ const now = new Date().toISOString();
20
+ const lines = [
21
+ '---',
22
+ 'kanban-plugin: basic',
23
+ 'reagent_managed: true',
24
+ `reagent_synced_at: "${now}"`,
25
+ '---',
26
+ '',
27
+ ];
28
+ // Group tasks by column
29
+ const columns = new Map();
30
+ for (const col of COLUMN_ORDER) {
31
+ columns.set(col, []);
32
+ }
33
+ for (const task of tasks) {
34
+ const column = STATUS_TO_COLUMN[task.status] || 'Backlog';
35
+ columns.get(column).push(task);
36
+ }
37
+ for (const col of COLUMN_ORDER) {
38
+ const colTasks = columns.get(col);
39
+ lines.push(`## ${col}`);
40
+ lines.push('');
41
+ if (colTasks.length === 0) {
42
+ lines.push('');
43
+ continue;
44
+ }
45
+ for (const task of colTasks) {
46
+ const checked = task.status === 'completed' || task.status === 'cancelled' ? 'x' : ' ';
47
+ let line = `- [${checked}] **${task.id}** ${task.title}`;
48
+ if (task.status === 'blocked' && task.blocked_by) {
49
+ line += ` *(blocked by: ${task.blocked_by})*`;
50
+ }
51
+ if (task.urgency === 'critical') {
52
+ line += ' 🔴';
53
+ }
54
+ if (task.assignee) {
55
+ line += ` @${task.assignee}`;
56
+ }
57
+ lines.push(line);
58
+ }
59
+ lines.push('');
60
+ }
61
+ return lines.join('\n');
62
+ }
63
+ //# sourceMappingURL=kanban-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kanban-generator.js","sourceRoot":"","sources":["../../src/obsidian/kanban-generator.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,CAAU,CAAC;AAEzF,MAAM,gBAAgB,GAA8D;IAClF,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,aAAa;IACtB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,MAAM;IACjB,SAAS,EAAE,WAAW;CACvB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAiB;IAC9C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,MAAM,KAAK,GAAa;QACtB,KAAK;QACL,sBAAsB;QACtB,uBAAuB;QACvB,uBAAuB,GAAG,GAAG;QAC7B,KAAK;QACL,EAAE;KACH,CAAC;IAEF,wBAAwB;IACxB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEf,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,KAAK,WAAW,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YACvF,IAAI,IAAI,GAAG,MAAM,OAAO,OAAO,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YAEzD,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjD,IAAI,IAAI,kBAAkB,IAAI,CAAC,UAAU,IAAI,CAAC;YAChD,CAAC;YAED,IAAI,IAAI,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;gBAChC,IAAI,IAAI,KAAK,CAAC;YAChB,CAAC;YAED,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClB,IAAI,IAAI,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC/B,CAAC;YAED,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,142 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schema for the obsidian_vault block in gateway.yaml.
4
+ * Every sync target defaults to false — fully opt-in.
5
+ */
6
+ export declare const ObsidianVaultConfigSchema: z.ZodObject<{
7
+ enabled: z.ZodDefault<z.ZodBoolean>;
8
+ vault_path: z.ZodOptional<z.ZodString>;
9
+ vault_name: z.ZodOptional<z.ZodString>;
10
+ paths: z.ZodDefault<z.ZodObject<{
11
+ root: z.ZodDefault<z.ZodString>;
12
+ kanban: z.ZodDefault<z.ZodString>;
13
+ sources: z.ZodDefault<z.ZodString>;
14
+ wiki: z.ZodDefault<z.ZodString>;
15
+ tasks: z.ZodDefault<z.ZodString>;
16
+ sessions: z.ZodDefault<z.ZodString>;
17
+ }, "strip", z.ZodTypeAny, {
18
+ tasks: string;
19
+ root: string;
20
+ kanban: string;
21
+ sources: string;
22
+ wiki: string;
23
+ sessions: string;
24
+ }, {
25
+ tasks?: string | undefined;
26
+ root?: string | undefined;
27
+ kanban?: string | undefined;
28
+ sources?: string | undefined;
29
+ wiki?: string | undefined;
30
+ sessions?: string | undefined;
31
+ }>>;
32
+ sync: z.ZodDefault<z.ZodObject<{
33
+ kanban: z.ZodDefault<z.ZodBoolean>;
34
+ context_dump: z.ZodDefault<z.ZodBoolean>;
35
+ wiki_refresh: z.ZodDefault<z.ZodBoolean>;
36
+ journal: z.ZodDefault<z.ZodBoolean>;
37
+ precompact: z.ZodDefault<z.ZodBoolean>;
38
+ tasks: z.ZodDefault<z.ZodBoolean>;
39
+ }, "strip", z.ZodTypeAny, {
40
+ tasks: boolean;
41
+ kanban: boolean;
42
+ context_dump: boolean;
43
+ wiki_refresh: boolean;
44
+ journal: boolean;
45
+ precompact: boolean;
46
+ }, {
47
+ tasks?: boolean | undefined;
48
+ kanban?: boolean | undefined;
49
+ context_dump?: boolean | undefined;
50
+ wiki_refresh?: boolean | undefined;
51
+ journal?: boolean | undefined;
52
+ precompact?: boolean | undefined;
53
+ }>>;
54
+ precompact: z.ZodDefault<z.ZodObject<{
55
+ engine: z.ZodDefault<z.ZodEnum<["claude", "ollama"]>>;
56
+ model: z.ZodDefault<z.ZodNullable<z.ZodString>>;
57
+ }, "strip", z.ZodTypeAny, {
58
+ model: string | null;
59
+ engine: "claude" | "ollama";
60
+ }, {
61
+ model?: string | null | undefined;
62
+ engine?: "claude" | "ollama" | undefined;
63
+ }>>;
64
+ }, "strip", z.ZodTypeAny, {
65
+ enabled: boolean;
66
+ paths: {
67
+ tasks: string;
68
+ root: string;
69
+ kanban: string;
70
+ sources: string;
71
+ wiki: string;
72
+ sessions: string;
73
+ };
74
+ precompact: {
75
+ model: string | null;
76
+ engine: "claude" | "ollama";
77
+ };
78
+ sync: {
79
+ tasks: boolean;
80
+ kanban: boolean;
81
+ context_dump: boolean;
82
+ wiki_refresh: boolean;
83
+ journal: boolean;
84
+ precompact: boolean;
85
+ };
86
+ vault_path?: string | undefined;
87
+ vault_name?: string | undefined;
88
+ }, {
89
+ enabled?: boolean | undefined;
90
+ vault_path?: string | undefined;
91
+ vault_name?: string | undefined;
92
+ paths?: {
93
+ tasks?: string | undefined;
94
+ root?: string | undefined;
95
+ kanban?: string | undefined;
96
+ sources?: string | undefined;
97
+ wiki?: string | undefined;
98
+ sessions?: string | undefined;
99
+ } | undefined;
100
+ precompact?: {
101
+ model?: string | null | undefined;
102
+ engine?: "claude" | "ollama" | undefined;
103
+ } | undefined;
104
+ sync?: {
105
+ tasks?: boolean | undefined;
106
+ kanban?: boolean | undefined;
107
+ context_dump?: boolean | undefined;
108
+ wiki_refresh?: boolean | undefined;
109
+ journal?: boolean | undefined;
110
+ precompact?: boolean | undefined;
111
+ } | undefined;
112
+ }>;
113
+ export type ObsidianVaultConfig = z.infer<typeof ObsidianVaultConfigSchema>;
114
+ /**
115
+ * Resolved config with an absolute vault_path guaranteed present.
116
+ * Only returned when the integration is fully enabled and the vault path resolves.
117
+ */
118
+ export interface ResolvedObsidianConfig {
119
+ vault_path: string;
120
+ vault_name?: string;
121
+ paths: ObsidianVaultConfig['paths'];
122
+ sync: ObsidianVaultConfig['sync'];
123
+ precompact: ObsidianVaultConfig['precompact'];
124
+ }
125
+ /**
126
+ * Load obsidian_vault config from .reagent/gateway.yaml.
127
+ *
128
+ * Resolution order for vault_path:
129
+ * 1. REAGENT_OBSIDIAN_VAULT env var (absolute path to vault root)
130
+ * 2. obsidian_vault.vault_path from gateway.yaml
131
+ * 3. null → disabled
132
+ *
133
+ * Returns null if:
134
+ * - No obsidian_vault block in gateway.yaml
135
+ * - enabled: false
136
+ * - No vault path resolvable
137
+ * - Vault directory does not exist
138
+ *
139
+ * Never throws — fail-silent like Discord notifier.
140
+ */
141
+ export declare function loadObsidianConfig(baseDir: string): ResolvedObsidianConfig | null;
142
+ //# sourceMappingURL=vault-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault-config.d.ts","sourceRoot":"","sources":["../../src/obsidian/vault-config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;GAGG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAClC,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC;CAC/C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,sBAAsB,GAAG,IAAI,CAoCjF"}
@@ -0,0 +1,92 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { z } from 'zod';
4
+ import { parse as parseYaml } from 'yaml';
5
+ /**
6
+ * Zod schema for the obsidian_vault block in gateway.yaml.
7
+ * Every sync target defaults to false — fully opt-in.
8
+ */
9
+ export const ObsidianVaultConfigSchema = z.object({
10
+ enabled: z.boolean().default(false),
11
+ vault_path: z.string().optional(),
12
+ vault_name: z.string().optional(),
13
+ paths: z
14
+ .object({
15
+ root: z.string().default('Projects/Reagent'),
16
+ kanban: z.string().default('Projects/Reagent/Kanban.md'),
17
+ sources: z.string().default('Projects/Reagent/Sources'),
18
+ wiki: z.string().default('Projects/Reagent/Auto'),
19
+ tasks: z.string().default('Tasks'),
20
+ sessions: z.string().default('Wiki/Sessions'),
21
+ })
22
+ .default({}),
23
+ sync: z
24
+ .object({
25
+ kanban: z.boolean().default(false),
26
+ context_dump: z.boolean().default(false),
27
+ wiki_refresh: z.boolean().default(false),
28
+ journal: z.boolean().default(true),
29
+ precompact: z.boolean().default(false),
30
+ tasks: z.boolean().default(true),
31
+ })
32
+ .default({}),
33
+ precompact: z
34
+ .object({
35
+ engine: z.enum(['claude', 'ollama']).default('claude'),
36
+ model: z.string().nullable().default(null),
37
+ })
38
+ .default({}),
39
+ });
40
+ /**
41
+ * Load obsidian_vault config from .reagent/gateway.yaml.
42
+ *
43
+ * Resolution order for vault_path:
44
+ * 1. REAGENT_OBSIDIAN_VAULT env var (absolute path to vault root)
45
+ * 2. obsidian_vault.vault_path from gateway.yaml
46
+ * 3. null → disabled
47
+ *
48
+ * Returns null if:
49
+ * - No obsidian_vault block in gateway.yaml
50
+ * - enabled: false
51
+ * - No vault path resolvable
52
+ * - Vault directory does not exist
53
+ *
54
+ * Never throws — fail-silent like Discord notifier.
55
+ */
56
+ export function loadObsidianConfig(baseDir) {
57
+ const gatewayPath = path.join(baseDir, '.reagent', 'gateway.yaml');
58
+ if (!fs.existsSync(gatewayPath))
59
+ return null;
60
+ try {
61
+ const raw = fs.readFileSync(gatewayPath, 'utf8');
62
+ const parsed = parseYaml(raw);
63
+ const obsidianRaw = parsed?.obsidian_vault;
64
+ if (!obsidianRaw)
65
+ return null;
66
+ const config = ObsidianVaultConfigSchema.parse(obsidianRaw);
67
+ if (!config.enabled)
68
+ return null;
69
+ // Resolve vault path: env var takes precedence
70
+ const vaultPath = process.env['REAGENT_OBSIDIAN_VAULT'] || config.vault_path;
71
+ if (!vaultPath)
72
+ return null;
73
+ // Resolve ~ to home directory
74
+ const resolvedPath = vaultPath.startsWith('~')
75
+ ? path.join(process.env['HOME'] || '', vaultPath.slice(1))
76
+ : vaultPath;
77
+ // Vault directory must exist
78
+ if (!fs.existsSync(resolvedPath))
79
+ return null;
80
+ return {
81
+ vault_path: resolvedPath,
82
+ vault_name: config.vault_name,
83
+ paths: config.paths,
84
+ sync: config.sync,
85
+ precompact: config.precompact,
86
+ };
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ //# sourceMappingURL=vault-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault-config.js","sourceRoot":"","sources":["../../src/obsidian/vault-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { type ResolvedObsidianConfig } from './vault-config.js';
2
+ export interface SyncResult {
3
+ target: string;
4
+ written: boolean;
5
+ path?: string;
6
+ error?: string;
7
+ }
8
+ /**
9
+ * Obsidian vault writer — writes markdown files directly to the vault.
10
+ * Obsidian auto-refreshes via filesystem watcher.
11
+ *
12
+ * All methods return silently when not configured.
13
+ * Pattern: DiscordNotifier (fail-silent, config-gated).
14
+ */
15
+ export declare class VaultWriter {
16
+ private readonly config;
17
+ private readonly store;
18
+ constructor(baseDir: string);
19
+ /**
20
+ * Check whether Obsidian vault sync is enabled.
21
+ */
22
+ isEnabled(): boolean;
23
+ /**
24
+ * Get resolved config for status display. Returns null if not configured.
25
+ */
26
+ getConfig(): ResolvedObsidianConfig | null;
27
+ /**
28
+ * Sync the Kanban board to the vault.
29
+ * Writes only if sync.kanban is enabled.
30
+ */
31
+ syncKanban(dryRun?: boolean): SyncResult;
32
+ /**
33
+ * Sync context dump to the vault.
34
+ * Placeholder for Phase 3.
35
+ */
36
+ syncContextDump(dryRun?: boolean): SyncResult;
37
+ /**
38
+ * Sync wiki pages to the vault.
39
+ * Placeholder for Phase 4.
40
+ */
41
+ syncWiki(dryRun?: boolean): SyncResult;
42
+ /**
43
+ * Sync tasks as individual Obsidian notes via the CLI.
44
+ * Writes only if sync.tasks is enabled and the Obsidian CLI is available.
45
+ */
46
+ syncTasks(dryRun?: boolean): SyncResult;
47
+ /**
48
+ * Sync all enabled targets.
49
+ */
50
+ syncAll(dryRun?: boolean): SyncResult[];
51
+ }
52
+ //# sourceMappingURL=vault-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault-writer.d.ts","sourceRoot":"","sources":["../../src/obsidian/vault-writer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAKpF,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;gBAEtB,OAAO,EAAE,MAAM;IAK3B;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,SAAS,IAAI,sBAAsB,GAAG,IAAI;IAI1C;;;OAGG;IACH,UAAU,CAAC,MAAM,UAAQ,GAAG,UAAU;IAyBtC;;;OAGG;IACH,eAAe,CAAC,MAAM,UAAQ,GAAG,UAAU;IAiB3C;;;OAGG;IACH,QAAQ,CAAC,MAAM,UAAQ,GAAG,UAAU;IAapC;;;OAGG;IACH,SAAS,CAAC,MAAM,UAAQ,GAAG,UAAU;IA+DrC;;OAEG;IACH,OAAO,CAAC,MAAM,UAAQ,GAAG,UAAU,EAAE;CAQtC"}
@@ -0,0 +1,166 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { loadObsidianConfig } from './vault-config.js';
4
+ import { generateKanban } from './kanban-generator.js';
5
+ import { ObsidianCli } from './cli.js';
6
+ import { TaskStore } from '../pm/task-store.js';
7
+ /**
8
+ * Obsidian vault writer — writes markdown files directly to the vault.
9
+ * Obsidian auto-refreshes via filesystem watcher.
10
+ *
11
+ * All methods return silently when not configured.
12
+ * Pattern: DiscordNotifier (fail-silent, config-gated).
13
+ */
14
+ export class VaultWriter {
15
+ config;
16
+ store;
17
+ constructor(baseDir) {
18
+ this.config = loadObsidianConfig(baseDir);
19
+ this.store = new TaskStore(baseDir);
20
+ }
21
+ /**
22
+ * Check whether Obsidian vault sync is enabled.
23
+ */
24
+ isEnabled() {
25
+ return this.config !== null;
26
+ }
27
+ /**
28
+ * Get resolved config for status display. Returns null if not configured.
29
+ */
30
+ getConfig() {
31
+ return this.config;
32
+ }
33
+ /**
34
+ * Sync the Kanban board to the vault.
35
+ * Writes only if sync.kanban is enabled.
36
+ */
37
+ syncKanban(dryRun = false) {
38
+ if (!this.config) {
39
+ return { target: 'kanban', written: false };
40
+ }
41
+ if (!this.config.sync.kanban) {
42
+ return { target: 'kanban', written: false, error: 'kanban sync not enabled in config' };
43
+ }
44
+ try {
45
+ const tasks = this.store.listTasks();
46
+ const markdown = generateKanban(tasks);
47
+ const outputPath = path.join(this.config.vault_path, this.config.paths.kanban);
48
+ if (!dryRun) {
49
+ // Ensure parent directory exists
50
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
51
+ fs.writeFileSync(outputPath, markdown, 'utf8');
52
+ }
53
+ return { target: 'kanban', written: !dryRun, path: outputPath };
54
+ }
55
+ catch {
56
+ return { target: 'kanban', written: false, error: 'Failed to write kanban' };
57
+ }
58
+ }
59
+ /**
60
+ * Sync context dump to the vault.
61
+ * Placeholder for Phase 3.
62
+ */
63
+ syncContextDump(dryRun = false) {
64
+ if (!this.config) {
65
+ return { target: 'context', written: false };
66
+ }
67
+ if (!this.config.sync.context_dump) {
68
+ return {
69
+ target: 'context',
70
+ written: false,
71
+ error: 'context_dump sync not enabled in config',
72
+ };
73
+ }
74
+ // Phase 3: context-dumper.ts will be wired here
75
+ void dryRun;
76
+ return { target: 'context', written: false, error: 'Not yet implemented' };
77
+ }
78
+ /**
79
+ * Sync wiki pages to the vault.
80
+ * Placeholder for Phase 4.
81
+ */
82
+ syncWiki(dryRun = false) {
83
+ if (!this.config) {
84
+ return { target: 'wiki', written: false };
85
+ }
86
+ if (!this.config.sync.wiki_refresh) {
87
+ return { target: 'wiki', written: false, error: 'wiki_refresh sync not enabled in config' };
88
+ }
89
+ // Phase 4: wiki-generator.ts will be wired here
90
+ void dryRun;
91
+ return { target: 'wiki', written: false, error: 'Not yet implemented' };
92
+ }
93
+ /**
94
+ * Sync tasks as individual Obsidian notes via the CLI.
95
+ * Writes only if sync.tasks is enabled and the Obsidian CLI is available.
96
+ */
97
+ syncTasks(dryRun = false) {
98
+ if (!this.config) {
99
+ return { target: 'tasks', written: false };
100
+ }
101
+ if (!this.config.sync.tasks) {
102
+ return { target: 'tasks', written: false, error: 'tasks sync not enabled in config' };
103
+ }
104
+ if (!this.config.vault_name) {
105
+ return { target: 'tasks', written: false, error: 'vault_name not set in config' };
106
+ }
107
+ if (!ObsidianCli.isAvailable()) {
108
+ return { target: 'tasks', written: false, error: 'Obsidian CLI not available' };
109
+ }
110
+ try {
111
+ const tasks = this.store.listTasks();
112
+ if (tasks.length === 0) {
113
+ return { target: 'tasks', written: false, error: 'No tasks to sync' };
114
+ }
115
+ if (dryRun) {
116
+ return { target: 'tasks', written: false, path: `${tasks.length} tasks would be synced` };
117
+ }
118
+ const cli = new ObsidianCli(this.config.vault_name);
119
+ const tasksPath = this.config.paths.tasks;
120
+ let synced = 0;
121
+ for (const task of tasks) {
122
+ const noteName = `${task.id} ${task.title}`;
123
+ const content = [
124
+ '---',
125
+ 'reagent_managed: true',
126
+ `task_id: "${task.id}"`,
127
+ `status: "${task.status}"`,
128
+ `urgency: "${task.urgency}"`,
129
+ `assignee: "${task.assignee || 'unassigned'}"`,
130
+ '---',
131
+ '',
132
+ `# ${task.title}`,
133
+ '',
134
+ `- **ID:** ${task.id}`,
135
+ `- **Status:** ${task.status}`,
136
+ `- **Urgency:** ${task.urgency}`,
137
+ `- **Assignee:** ${task.assignee || 'unassigned'}`,
138
+ '',
139
+ ].join('\n');
140
+ if (cli.createNote(noteName, content, { path: tasksPath })) {
141
+ synced++;
142
+ }
143
+ }
144
+ return {
145
+ target: 'tasks',
146
+ written: synced > 0,
147
+ path: `${synced}/${tasks.length} tasks synced to ${tasksPath}`,
148
+ };
149
+ }
150
+ catch {
151
+ return { target: 'tasks', written: false, error: 'Failed to sync tasks' };
152
+ }
153
+ }
154
+ /**
155
+ * Sync all enabled targets.
156
+ */
157
+ syncAll(dryRun = false) {
158
+ return [
159
+ this.syncKanban(dryRun),
160
+ this.syncContextDump(dryRun),
161
+ this.syncWiki(dryRun),
162
+ this.syncTasks(dryRun),
163
+ ];
164
+ }
165
+ }
166
+ //# sourceMappingURL=vault-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault-writer.js","sourceRoot":"","sources":["../../src/obsidian/vault-writer.ts"],"names":[],"mappings":"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"}
@@ -11,5 +11,9 @@ export interface Policy {
11
11
  blocked_paths: string[];
12
12
  notification_channel: string;
13
13
  injection_detection?: 'block' | 'warn';
14
+ context_protection?: {
15
+ delegate_to_subagent: string[];
16
+ max_bash_output_lines?: number;
17
+ };
14
18
  }
15
19
  //# sourceMappingURL=policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/types/policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,aAAa,CAAC;IAC9B,kBAAkB,EAAE,aAAa,CAAC;IAClC,iCAAiC,EAAE,OAAO,CAAC;IAC3C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;IAE7B,mBAAmB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACxC"}
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/types/policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,aAAa,CAAC;IAC9B,kBAAkB,EAAE,aAAa,CAAC;IAClC,iCAAiC,EAAE,OAAO,CAAC;IAC3C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;IAE7B,mBAAmB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAEvC,kBAAkB,CAAC,EAAE;QACnB,oBAAoB,EAAE,MAAM,EAAE,CAAC;QAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;CACH"}
@@ -261,6 +261,49 @@ if printf '%s' "$CMD" | grep -qiE '(alias|function)[[:space:]]+[a-zA-Z_]+.*(--(n
261
261
  "Alt: Do not wrap bypass patterns in aliases or functions."
262
262
  fi
263
263
 
264
+ # H17: context_protection — block commands that should be delegated to subagents
265
+ # Reads context_protection.delegate_to_subagent from .reagent/policy.yaml.
266
+ # These commands produce excessive output that exhausts coordinator context windows.
267
+ POLICY_FILE="${REAGENT_ROOT}/.reagent/policy.yaml"
268
+ if [[ -f "$POLICY_FILE" ]]; then
269
+ DELEGATE_PATTERNS=()
270
+ IN_DELEGATE_BLOCK=0
271
+ while IFS= read -r line; do
272
+ if printf '%s' "$line" | grep -qE '^[[:space:]]*delegate_to_subagent:'; then
273
+ # Check for inline empty array
274
+ if printf '%s' "$line" | grep -qE 'delegate_to_subagent:[[:space:]]*\[\]'; then
275
+ break
276
+ fi
277
+ IN_DELEGATE_BLOCK=1
278
+ continue
279
+ fi
280
+ if [[ $IN_DELEGATE_BLOCK -eq 1 ]]; then
281
+ # Block sequence items start with " - "
282
+ if printf '%s' "$line" | grep -qE '^[[:space:]]*-[[:space:]]'; then
283
+ pattern=$(printf '%s' "$line" | sed "s/^[[:space:]]*-[[:space:]]*//; s/^[\"']//; s/[\"']$//")
284
+ if [[ -n "$pattern" ]]; then
285
+ DELEGATE_PATTERNS+=("$pattern")
286
+ fi
287
+ else
288
+ # Non-continuation line = end of block
289
+ break
290
+ fi
291
+ fi
292
+ done < "$POLICY_FILE"
293
+
294
+ for pattern in "${DELEGATE_PATTERNS[@]+"${DELEGATE_PATTERNS[@]}"}"; do
295
+ # Use fixed-string match — these are command prefixes, not regex
296
+ if printf '%s' "$CMD" | grep -qF "$pattern"; then
297
+ add_high \
298
+ "Context protection — command must run in a subagent" \
299
+ "This command produces excessive output that will exhaust the coordinator context window. Delegate it to a subagent instead of running it directly." \
300
+ "Alt: Use the Agent tool to delegate: Agent(subagent_type: 'qa-engineer-automation', prompt: 'Run $pattern and report pass/fail summary only.')" \
301
+ "Alt: The context_protection policy in .reagent/policy.yaml lists commands that must be delegated."
302
+ break
303
+ fi
304
+ done
305
+ fi
306
+
264
307
  # ── 10. MEDIUM severity checks ────────────────────────────────────────────────
265
308
 
266
309
  # M1: npm install --force