@redaksjon/protokoll 0.0.11 → 0.0.13

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 (88) hide show
  1. package/.cursor/rules/definition-of-done.md +1 -0
  2. package/.cursor/rules/no-emoticons.md +26 -12
  3. package/README.md +483 -69
  4. package/dist/agentic/executor.js +473 -41
  5. package/dist/agentic/executor.js.map +1 -1
  6. package/dist/agentic/index.js.map +1 -1
  7. package/dist/agentic/tools/lookup-person.js +123 -4
  8. package/dist/agentic/tools/lookup-person.js.map +1 -1
  9. package/dist/agentic/tools/lookup-project.js +139 -22
  10. package/dist/agentic/tools/lookup-project.js.map +1 -1
  11. package/dist/agentic/tools/route-note.js +5 -1
  12. package/dist/agentic/tools/route-note.js.map +1 -1
  13. package/dist/arguments.js +6 -3
  14. package/dist/arguments.js.map +1 -1
  15. package/dist/cli/action.js +704 -0
  16. package/dist/cli/action.js.map +1 -0
  17. package/dist/cli/config.js +482 -0
  18. package/dist/cli/config.js.map +1 -0
  19. package/dist/cli/context.js +466 -0
  20. package/dist/cli/context.js.map +1 -0
  21. package/dist/cli/feedback.js +858 -0
  22. package/dist/cli/feedback.js.map +1 -0
  23. package/dist/cli/index.js +103 -0
  24. package/dist/cli/index.js.map +1 -0
  25. package/dist/cli/install.js +572 -0
  26. package/dist/cli/install.js.map +1 -0
  27. package/dist/cli/transcript.js +199 -0
  28. package/dist/cli/transcript.js.map +1 -0
  29. package/dist/constants.js +12 -5
  30. package/dist/constants.js.map +1 -1
  31. package/dist/context/discovery.js +1 -1
  32. package/dist/context/discovery.js.map +1 -1
  33. package/dist/context/index.js +25 -1
  34. package/dist/context/index.js.map +1 -1
  35. package/dist/context/storage.js +57 -4
  36. package/dist/context/storage.js.map +1 -1
  37. package/dist/interactive/handler.js +310 -9
  38. package/dist/interactive/handler.js.map +1 -1
  39. package/dist/main.js +11 -1
  40. package/dist/main.js.map +1 -1
  41. package/dist/output/index.js.map +1 -1
  42. package/dist/output/manager.js +47 -2
  43. package/dist/output/manager.js.map +1 -1
  44. package/dist/phases/complete.js +38 -3
  45. package/dist/phases/complete.js.map +1 -1
  46. package/dist/phases/locate.js +1 -1
  47. package/dist/phases/locate.js.map +1 -1
  48. package/dist/pipeline/orchestrator.js +104 -31
  49. package/dist/pipeline/orchestrator.js.map +1 -1
  50. package/dist/protokoll.js +68 -2
  51. package/dist/protokoll.js.map +1 -1
  52. package/dist/reasoning/client.js +83 -0
  53. package/dist/reasoning/client.js.map +1 -1
  54. package/dist/reasoning/index.js +1 -0
  55. package/dist/reasoning/index.js.map +1 -1
  56. package/dist/routing/router.js +2 -2
  57. package/dist/routing/router.js.map +1 -1
  58. package/dist/util/media.js +1 -1
  59. package/dist/util/media.js.map +1 -1
  60. package/dist/util/metadata.js.map +1 -1
  61. package/dist/util/sound.js +116 -0
  62. package/dist/util/sound.js.map +1 -0
  63. package/dist/util/storage.js +3 -3
  64. package/dist/util/storage.js.map +1 -1
  65. package/docs/duplicate-question-prevention.md +117 -0
  66. package/docs/examples.md +152 -0
  67. package/docs/interactive-context-example.md +92 -0
  68. package/docs/package-lock.json +6 -0
  69. package/docs/package.json +3 -1
  70. package/eslint.config.mjs +1 -1
  71. package/guide/action.md +375 -0
  72. package/guide/config.md +207 -0
  73. package/guide/configuration.md +82 -67
  74. package/guide/context-commands.md +574 -0
  75. package/guide/context-system.md +20 -7
  76. package/guide/development.md +106 -4
  77. package/guide/feedback.md +335 -0
  78. package/guide/index.md +100 -4
  79. package/guide/interactive.md +15 -14
  80. package/guide/quickstart.md +21 -7
  81. package/guide/reasoning.md +18 -4
  82. package/guide/routing.md +192 -97
  83. package/package.json +2 -3
  84. package/scripts/copy-assets.mjs +47 -0
  85. package/scripts/coverage-priority.mjs +323 -0
  86. package/tsconfig.tsbuildinfo +1 -1
  87. package/vite.config.ts +6 -13
  88. package/vitest.config.ts +5 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/context/index.ts"],"sourcesContent":["/**\n * Context System\n * \n * Main entry point for the context system. Provides a factory function\n * to create context instances that can discover, load, and manage\n * entity data from hierarchical .protokoll directories.\n * \n * Design Note: This module is designed to be self-contained and may be\n * extracted for use in other tools (kronologi, observasjon) in the future.\n */\n\nimport { \n Entity, \n Person, \n Project, \n Company, \n Term, \n ContextDiscoveryOptions,\n DiscoveredContextDir,\n HierarchicalContextResult \n} from './types';\nimport * as Storage from './storage';\nimport * as Discovery from './discovery';\n\nexport interface ContextInstance {\n // Initialization\n load(): Promise<void>;\n reload(): Promise<void>;\n \n // Discovery info\n getDiscoveredDirs(): DiscoveredContextDir[];\n getConfig(): Record<string, unknown>;\n \n // Entity access\n getPerson(id: string): Person | undefined;\n getProject(id: string): Project | undefined;\n getCompany(id: string): Company | undefined;\n getTerm(id: string): Term | undefined;\n \n getAllPeople(): Person[];\n getAllProjects(): Project[];\n getAllCompanies(): Company[];\n getAllTerms(): Term[];\n \n // Search\n search(query: string): Entity[];\n findBySoundsLike(phonetic: string): Entity | undefined;\n \n // Modification (for self-update mode)\n saveEntity(entity: Entity): Promise<void>;\n \n // Check if context is available\n hasContext(): boolean;\n}\n\nexport interface CreateOptions {\n startingDir?: string;\n configDirName?: string;\n configFileName?: string;\n}\n\n/**\n * Create a new context instance\n */\nexport const create = async (options: CreateOptions = {}): Promise<ContextInstance> => {\n const discoveryOptions: ContextDiscoveryOptions = {\n configDirName: options.configDirName ?? '.protokoll',\n configFileName: options.configFileName ?? 'config.yaml',\n startingDir: options.startingDir,\n };\n\n const storage = Storage.create();\n let discoveryResult: HierarchicalContextResult = {\n config: {},\n discoveredDirs: [],\n contextDirs: [],\n };\n\n const loadContext = async (): Promise<void> => {\n discoveryResult = await Discovery.loadHierarchicalConfig(discoveryOptions);\n storage.clear();\n await storage.load(discoveryResult.contextDirs);\n };\n\n // Initial load\n await loadContext();\n\n return {\n load: loadContext,\n \n reload: async () => {\n storage.clear();\n await storage.load(discoveryResult.contextDirs);\n },\n \n getDiscoveredDirs: () => discoveryResult.discoveredDirs,\n getConfig: () => discoveryResult.config,\n \n getPerson: (id) => storage.get<Person>('person', id),\n getProject: (id) => storage.get<Project>('project', id),\n getCompany: (id) => storage.get<Company>('company', id),\n getTerm: (id) => storage.get<Term>('term', id),\n \n getAllPeople: () => storage.getAll<Person>('person'),\n getAllProjects: () => storage.getAll<Project>('project'),\n getAllCompanies: () => storage.getAll<Company>('company'),\n getAllTerms: () => storage.getAll<Term>('term'),\n \n search: (query) => storage.search(query),\n findBySoundsLike: (phonetic) => storage.findBySoundsLike(phonetic),\n \n saveEntity: async (entity) => {\n // Save to the closest .protokoll directory\n const closestDir = discoveryResult.discoveredDirs\n .sort((a, b) => a.level - b.level)[0];\n \n if (!closestDir) {\n throw new Error('No .protokoll directory found. Run with --setup to create one.');\n }\n \n await storage.save(entity, closestDir.path);\n },\n \n hasContext: () => discoveryResult.discoveredDirs.length > 0,\n };\n};\n\n// Re-export types\nexport * from './types';\n\n// Re-export discovery utilities for direct use if needed\nexport { discoverConfigDirectories, loadHierarchicalConfig, deepMerge } from './discovery';\n\n"],"names":["create","options","discoveryOptions","configDirName","configFileName","startingDir","storage","Storage","discoveryResult","config","discoveredDirs","contextDirs","loadContext","Discovery","clear","load","reload","getDiscoveredDirs","getConfig","getPerson","id","get","getProject","getCompany","getTerm","getAllPeople","getAll","getAllProjects","getAllCompanies","getAllTerms","search","query","findBySoundsLike","phonetic","saveEntity","entity","closestDir","sort","a","b","level","Error","save","path","hasContext","length"],"mappings":";;;;AA6DA;;AAEC,IACM,MAAMA,MAAAA,GAAS,OAAOC,OAAAA,GAAyB,EAAE,GAAA;QAEjCA,sBAAAA,EACCA,uBAAAA;AAFpB,IAAA,MAAMC,gBAAAA,GAA4C;AAC9CC,QAAAA,aAAa,GAAEF,sBAAAA,GAAAA,OAAAA,CAAQE,aAAa,MAAA,IAAA,IAArBF,oCAAAA,sBAAAA,GAAyB,YAAA;AACxCG,QAAAA,cAAc,GAAEH,uBAAAA,GAAAA,OAAAA,CAAQG,cAAc,MAAA,IAAA,IAAtBH,qCAAAA,uBAAAA,GAA0B,aAAA;AAC1CI,QAAAA,WAAAA,EAAaJ,QAAQI;AACzB,KAAA;IAEA,MAAMC,OAAAA,GAAUC,QAAc,EAAA;AAC9B,IAAA,IAAIC,eAAAA,GAA6C;AAC7CC,QAAAA,MAAAA,EAAQ,EAAC;AACTC,QAAAA,cAAAA,EAAgB,EAAE;AAClBC,QAAAA,WAAAA,EAAa;AACjB,KAAA;AAEA,IAAA,MAAMC,WAAAA,GAAc,UAAA;QAChBJ,eAAAA,GAAkB,MAAMK,sBAAgC,CAACX,gBAAAA,CAAAA;AACzDI,QAAAA,OAAAA,CAAQQ,KAAK,EAAA;AACb,QAAA,MAAMR,OAAAA,CAAQS,IAAI,CAACP,eAAAA,CAAgBG,WAAW,CAAA;AAClD,IAAA,CAAA;;IAGA,MAAMC,WAAAA,EAAAA;IAEN,OAAO;QACHG,IAAAA,EAAMH,WAAAA;QAENI,MAAAA,EAAQ,UAAA;AACJV,YAAAA,OAAAA,CAAQQ,KAAK,EAAA;AACb,YAAA,MAAMR,OAAAA,CAAQS,IAAI,CAACP,eAAAA,CAAgBG,WAAW,CAAA;AAClD,QAAA,CAAA;QAEAM,iBAAAA,EAAmB,IAAMT,gBAAgBE,cAAc;QACvDQ,SAAAA,EAAW,IAAMV,gBAAgBC,MAAM;AAEvCU,QAAAA,SAAAA,EAAW,CAACC,EAAAA,GAAOd,OAAAA,CAAQe,GAAG,CAAS,QAAA,EAAUD,EAAAA,CAAAA;AACjDE,QAAAA,UAAAA,EAAY,CAACF,EAAAA,GAAOd,OAAAA,CAAQe,GAAG,CAAU,SAAA,EAAWD,EAAAA,CAAAA;AACpDG,QAAAA,UAAAA,EAAY,CAACH,EAAAA,GAAOd,OAAAA,CAAQe,GAAG,CAAU,SAAA,EAAWD,EAAAA,CAAAA;AACpDI,QAAAA,OAAAA,EAAS,CAACJ,EAAAA,GAAOd,OAAAA,CAAQe,GAAG,CAAO,MAAA,EAAQD,EAAAA,CAAAA;QAE3CK,YAAAA,EAAc,IAAMnB,OAAAA,CAAQoB,MAAM,CAAS,QAAA,CAAA;QAC3CC,cAAAA,EAAgB,IAAMrB,OAAAA,CAAQoB,MAAM,CAAU,SAAA,CAAA;QAC9CE,eAAAA,EAAiB,IAAMtB,OAAAA,CAAQoB,MAAM,CAAU,SAAA,CAAA;QAC/CG,WAAAA,EAAa,IAAMvB,OAAAA,CAAQoB,MAAM,CAAO,MAAA,CAAA;AAExCI,QAAAA,MAAAA,EAAQ,CAACC,KAAAA,GAAUzB,OAAAA,CAAQwB,MAAM,CAACC,KAAAA,CAAAA;AAClCC,QAAAA,gBAAAA,EAAkB,CAACC,QAAAA,GAAa3B,OAAAA,CAAQ0B,gBAAgB,CAACC,QAAAA,CAAAA;AAEzDC,QAAAA,UAAAA,EAAY,OAAOC,MAAAA,GAAAA;;AAEf,YAAA,MAAMC,aAAa5B,eAAAA,CAAgBE,cAAc,CAC5C2B,IAAI,CAAC,CAACC,CAAAA,EAAGC,CAAAA,GAAMD,CAAAA,CAAEE,KAAK,GAAGD,CAAAA,CAAEC,KAAK,CAAC,CAAC,CAAA,CAAE;AAEzC,YAAA,IAAI,CAACJ,UAAAA,EAAY;AACb,gBAAA,MAAM,IAAIK,KAAAA,CAAM,gEAAA,CAAA;AACpB,YAAA;AAEA,YAAA,MAAMnC,OAAAA,CAAQoC,IAAI,CAACP,MAAAA,EAAQC,WAAWO,IAAI,CAAA;AAC9C,QAAA,CAAA;AAEAC,QAAAA,UAAAA,EAAY,IAAMpC,eAAAA,CAAgBE,cAAc,CAACmC,MAAM,GAAG;AAC9D,KAAA;AACJ;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../src/context/index.ts"],"sourcesContent":["/**\n * Context System\n * \n * Main entry point for the context system. Provides a factory function\n * to create context instances that can discover, load, and manage\n * entity data from hierarchical .protokoll directories.\n * \n * Design Note: This module is designed to be self-contained and may be\n * extracted for use in other tools (kronologi, observasjon) in the future.\n */\n\nimport { \n Entity, \n Person, \n Project, \n Company, \n Term,\n IgnoredTerm,\n ContextDiscoveryOptions,\n DiscoveredContextDir,\n HierarchicalContextResult \n} from './types';\nimport * as Storage from './storage';\nimport * as Discovery from './discovery';\n\nexport interface ContextInstance {\n // Initialization\n load(): Promise<void>;\n reload(): Promise<void>;\n \n // Discovery info\n getDiscoveredDirs(): DiscoveredContextDir[];\n getConfig(): Record<string, unknown>;\n getContextDirs(): string[];\n \n // Entity access\n getPerson(id: string): Person | undefined;\n getProject(id: string): Project | undefined;\n getCompany(id: string): Company | undefined;\n getTerm(id: string): Term | undefined;\n getIgnored(id: string): IgnoredTerm | undefined;\n \n getAllPeople(): Person[];\n getAllProjects(): Project[];\n getAllCompanies(): Company[];\n getAllTerms(): Term[];\n getAllIgnored(): IgnoredTerm[];\n \n // Check if a term is ignored\n isIgnored(term: string): boolean;\n \n // Search\n search(query: string): Entity[];\n findBySoundsLike(phonetic: string): Entity | undefined;\n \n // Modification (for self-update mode)\n saveEntity(entity: Entity): Promise<void>;\n deleteEntity(entity: Entity): Promise<boolean>;\n getEntityFilePath(entity: Entity): string | undefined;\n \n // Check if context is available\n hasContext(): boolean;\n}\n\nexport interface CreateOptions {\n startingDir?: string;\n configDirName?: string;\n configFileName?: string;\n}\n\n/**\n * Create a new context instance\n */\nexport const create = async (options: CreateOptions = {}): Promise<ContextInstance> => {\n const discoveryOptions: ContextDiscoveryOptions = {\n configDirName: options.configDirName ?? '.protokoll',\n configFileName: options.configFileName ?? 'config.yaml',\n startingDir: options.startingDir,\n };\n\n const storage = Storage.create();\n let discoveryResult: HierarchicalContextResult = {\n config: {},\n discoveredDirs: [],\n contextDirs: [],\n };\n\n const loadContext = async (): Promise<void> => {\n discoveryResult = await Discovery.loadHierarchicalConfig(discoveryOptions);\n storage.clear();\n await storage.load(discoveryResult.contextDirs);\n };\n\n // Initial load\n await loadContext();\n\n return {\n load: loadContext,\n \n reload: async () => {\n storage.clear();\n await storage.load(discoveryResult.contextDirs);\n },\n \n getDiscoveredDirs: () => discoveryResult.discoveredDirs,\n getConfig: () => discoveryResult.config,\n getContextDirs: () => discoveryResult.contextDirs,\n \n getPerson: (id) => storage.get<Person>('person', id),\n getProject: (id) => storage.get<Project>('project', id),\n getCompany: (id) => storage.get<Company>('company', id),\n getTerm: (id) => storage.get<Term>('term', id),\n getIgnored: (id) => storage.get<IgnoredTerm>('ignored', id),\n \n getAllPeople: () => storage.getAll<Person>('person'),\n getAllProjects: () => storage.getAll<Project>('project'),\n getAllCompanies: () => storage.getAll<Company>('company'),\n getAllTerms: () => storage.getAll<Term>('term'),\n getAllIgnored: () => storage.getAll<IgnoredTerm>('ignored'),\n \n isIgnored: (term: string) => {\n const normalizedTerm = term.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');\n const ignoredTerms = storage.getAll<IgnoredTerm>('ignored');\n return ignoredTerms.some(ignored => \n ignored.id === normalizedTerm || \n ignored.name.toLowerCase() === term.toLowerCase()\n );\n },\n \n search: (query) => storage.search(query),\n findBySoundsLike: (phonetic) => storage.findBySoundsLike(phonetic),\n \n saveEntity: async (entity) => {\n // Save to the closest .protokoll directory\n const closestDir = discoveryResult.discoveredDirs\n .sort((a, b) => a.level - b.level)[0];\n \n if (!closestDir) {\n throw new Error('No .protokoll directory found. Run with --init-config to create one.');\n }\n \n await storage.save(entity, closestDir.path);\n },\n \n deleteEntity: async (entity) => {\n // Delete from the closest .protokoll directory that contains it\n const filePath = storage.getEntityFilePath(entity.type, entity.id, discoveryResult.contextDirs);\n if (!filePath) {\n return false;\n }\n \n // Extract the context directory from the file path\n const contextDir = discoveryResult.contextDirs.find(dir => filePath.startsWith(dir));\n if (!contextDir) {\n return false;\n }\n \n return storage.delete(entity.type, entity.id, contextDir);\n },\n \n getEntityFilePath: (entity) => {\n return storage.getEntityFilePath(entity.type, entity.id, discoveryResult.contextDirs);\n },\n \n hasContext: () => discoveryResult.discoveredDirs.length > 0,\n };\n};\n\n// Re-export types\nexport * from './types';\n\n// Re-export discovery utilities for direct use if needed\nexport { discoverConfigDirectories, loadHierarchicalConfig, deepMerge } from './discovery';\n\n"],"names":["create","options","discoveryOptions","configDirName","configFileName","startingDir","storage","Storage","discoveryResult","config","discoveredDirs","contextDirs","loadContext","Discovery","clear","load","reload","getDiscoveredDirs","getConfig","getContextDirs","getPerson","id","get","getProject","getCompany","getTerm","getIgnored","getAllPeople","getAll","getAllProjects","getAllCompanies","getAllTerms","getAllIgnored","isIgnored","term","normalizedTerm","toLowerCase","replace","ignoredTerms","some","ignored","name","search","query","findBySoundsLike","phonetic","saveEntity","entity","closestDir","sort","a","b","level","Error","save","path","deleteEntity","filePath","getEntityFilePath","type","contextDir","find","dir","startsWith","delete","hasContext","length"],"mappings":"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"}
@@ -1,18 +1,21 @@
1
1
  import * as yaml from 'js-yaml';
2
2
  import * as fs from 'fs/promises';
3
- import * as path from 'path';
3
+ import { existsSync, statSync } from 'fs';
4
+ import * as path from 'node:path';
4
5
 
5
6
  const DIRECTORY_TO_TYPE = {
6
7
  'people': 'person',
7
8
  'projects': 'project',
8
9
  'companies': 'company',
9
- 'terms': 'term'
10
+ 'terms': 'term',
11
+ 'ignored': 'ignored'
10
12
  };
11
13
  const TYPE_TO_DIRECTORY = {
12
14
  'person': 'people',
13
15
  'project': 'projects',
14
16
  'company': 'companies',
15
- 'term': 'terms'
17
+ 'term': 'terms',
18
+ 'ignored': 'ignored'
16
19
  };
17
20
  const create = ()=>{
18
21
  const entities = new Map([
@@ -31,6 +34,10 @@ const create = ()=>{
31
34
  [
32
35
  'term',
33
36
  new Map()
37
+ ],
38
+ [
39
+ 'ignored',
40
+ new Map()
34
41
  ]
35
42
  ]);
36
43
  const load = async (contextDirs)=>{
@@ -62,6 +69,7 @@ const create = ()=>{
62
69
  const save = async (entity, targetDir)=>{
63
70
  var _entities_get;
64
71
  const dirName = TYPE_TO_DIRECTORY[entity.type];
72
+ // Save to context subdirectory (context/people/, context/projects/, etc.)
65
73
  const dirPath = path.join(targetDir, 'context', dirName);
66
74
  await fs.mkdir(dirPath, {
67
75
  recursive: true
@@ -76,6 +84,49 @@ const create = ()=>{
76
84
  await fs.writeFile(filePath, content, 'utf-8');
77
85
  (_entities_get = entities.get(entity.type)) === null || _entities_get === void 0 ? void 0 : _entities_get.set(entity.id, entity);
78
86
  };
87
+ const deleteEntity = async (type, id, targetDir)=>{
88
+ const dirName = TYPE_TO_DIRECTORY[type];
89
+ // Try both possible locations (with and without 'context' subdirectory)
90
+ const possiblePaths = [
91
+ path.join(targetDir, dirName, `${id}.yaml`),
92
+ path.join(targetDir, dirName, `${id}.yml`),
93
+ path.join(targetDir, 'context', dirName, `${id}.yaml`),
94
+ path.join(targetDir, 'context', dirName, `${id}.yml`)
95
+ ];
96
+ for (const filePath of possiblePaths){
97
+ try {
98
+ var _entities_get;
99
+ await fs.unlink(filePath);
100
+ (_entities_get = entities.get(type)) === null || _entities_get === void 0 ? void 0 : _entities_get.delete(id);
101
+ return true;
102
+ } catch {
103
+ // File doesn't exist at this path, try next
104
+ }
105
+ }
106
+ return false;
107
+ };
108
+ const getEntityFilePath = (type, id, contextDirs)=>{
109
+ const dirName = TYPE_TO_DIRECTORY[type];
110
+ // Search in reverse order (closest first) to find where the entity is defined
111
+ for (const contextDir of [
112
+ ...contextDirs
113
+ ].reverse()){
114
+ const possiblePaths = [
115
+ path.join(contextDir, dirName, `${id}.yaml`),
116
+ path.join(contextDir, dirName, `${id}.yml`)
117
+ ];
118
+ for (const filePath of possiblePaths){
119
+ // Use sync access check - this is only for CLI, not hot path
120
+ if (existsSync(filePath)) {
121
+ const stat = statSync(filePath);
122
+ if (stat.isFile()) {
123
+ return filePath;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ return undefined;
129
+ };
79
130
  const get = (type, id)=>{
80
131
  var _entities_get;
81
132
  return (_entities_get = entities.get(type)) === null || _entities_get === void 0 ? void 0 : _entities_get.get(id);
@@ -119,11 +170,13 @@ const create = ()=>{
119
170
  return {
120
171
  load,
121
172
  save,
173
+ delete: deleteEntity,
122
174
  get,
123
175
  getAll,
124
176
  search,
125
177
  findBySoundsLike,
126
- clear
178
+ clear,
179
+ getEntityFilePath
127
180
  };
128
181
  };
129
182
 
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sources":["../../src/context/storage.ts"],"sourcesContent":["/**\n * Context Storage\n * \n * Handles loading and saving entity YAML files from context directories.\n * Supports hierarchical loading where later directories override earlier ones.\n * \n * Design Note: This module is designed to be self-contained and may be\n * extracted for use in other tools (kronologi, observasjon) in the future.\n */\n\nimport * as yaml from 'js-yaml';\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\nimport { Entity, EntityType } from './types';\n\nexport interface StorageInstance {\n load(contextDirs: string[]): Promise<void>;\n save(entity: Entity, targetDir: string): Promise<void>;\n get<T extends Entity>(type: EntityType, id: string): T | undefined;\n getAll<T extends Entity>(type: EntityType): T[];\n search(query: string): Entity[];\n findBySoundsLike(phonetic: string): Entity | undefined;\n clear(): void;\n}\n\ntype DirectoryName = 'people' | 'projects' | 'companies' | 'terms';\n\nconst DIRECTORY_TO_TYPE: Record<DirectoryName, EntityType> = {\n 'people': 'person',\n 'projects': 'project',\n 'companies': 'company',\n 'terms': 'term',\n};\n\nconst TYPE_TO_DIRECTORY: Record<EntityType, DirectoryName> = {\n 'person': 'people',\n 'project': 'projects',\n 'company': 'companies',\n 'term': 'terms',\n};\n\nexport const create = (): StorageInstance => {\n const entities: Map<EntityType, Map<string, Entity>> = new Map([\n ['person', new Map()],\n ['project', new Map()],\n ['company', new Map()],\n ['term', new Map()],\n ]);\n\n const load = async (contextDirs: string[]): Promise<void> => {\n // Load from all context directories (later directories override)\n for (const contextDir of contextDirs) {\n for (const dirName of Object.keys(DIRECTORY_TO_TYPE) as DirectoryName[]) {\n const typeDir = path.join(contextDir, dirName);\n const entityType = DIRECTORY_TO_TYPE[dirName];\n \n try {\n const files = await fs.readdir(typeDir);\n for (const file of files) {\n if (!file.endsWith('.yaml') && !file.endsWith('.yml')) continue;\n \n const content = await fs.readFile(path.join(typeDir, file), 'utf-8');\n const parsed = yaml.load(content) as Partial<Entity>;\n \n if (parsed && parsed.id) {\n entities.get(entityType)?.set(parsed.id, {\n ...parsed,\n type: entityType,\n } as Entity);\n }\n }\n } catch {\n // Directory doesn't exist, skip\n }\n }\n }\n };\n\n const save = async (entity: Entity, targetDir: string): Promise<void> => {\n const dirName = TYPE_TO_DIRECTORY[entity.type];\n const dirPath = path.join(targetDir, 'context', dirName);\n await fs.mkdir(dirPath, { recursive: true });\n \n const filePath = path.join(dirPath, `${entity.id}.yaml`);\n \n // Remove type from saved YAML (it's inferred from directory)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { type: _entityType, ...entityWithoutType } = entity;\n const content = yaml.dump(entityWithoutType, { lineWidth: -1 });\n await fs.writeFile(filePath, content, 'utf-8');\n \n entities.get(entity.type)?.set(entity.id, entity);\n };\n\n const get = <T extends Entity>(type: EntityType, id: string): T | undefined => {\n return entities.get(type)?.get(id) as T | undefined;\n };\n\n const getAll = <T extends Entity>(type: EntityType): T[] => {\n return Array.from(entities.get(type)?.values() ?? []) as T[];\n };\n\n const search = (query: string): Entity[] => {\n const normalizedQuery = query.toLowerCase();\n const results: Entity[] = [];\n \n for (const entityMap of entities.values()) {\n for (const entity of entityMap.values()) {\n if (entity.name.toLowerCase().includes(normalizedQuery)) {\n results.push(entity);\n }\n }\n }\n \n return results;\n };\n\n const findBySoundsLike = (phonetic: string): Entity | undefined => {\n const normalized = phonetic.toLowerCase().trim();\n \n for (const entityMap of entities.values()) {\n for (const entity of entityMap.values()) {\n // Check sounds_like field on entities that have it\n const entityWithSoundsLike = entity as Entity & { sounds_like?: string[] };\n const variants = entityWithSoundsLike.sounds_like;\n if (variants?.some(v => v.toLowerCase() === normalized)) {\n return entity;\n }\n }\n }\n \n return undefined;\n };\n\n const clear = (): void => {\n for (const entityMap of entities.values()) {\n entityMap.clear();\n }\n };\n\n return { load, save, get, getAll, search, findBySoundsLike, clear };\n};\n\n"],"names":["DIRECTORY_TO_TYPE","TYPE_TO_DIRECTORY","create","entities","Map","load","contextDirs","contextDir","dirName","Object","keys","typeDir","path","join","entityType","files","fs","readdir","file","endsWith","content","readFile","parsed","yaml","id","get","set","type","save","entity","targetDir","dirPath","mkdir","recursive","filePath","_entityType","entityWithoutType","dump","lineWidth","writeFile","getAll","Array","from","values","search","query","normalizedQuery","toLowerCase","results","entityMap","name","includes","push","findBySoundsLike","phonetic","normalized","trim","entityWithSoundsLike","variants","sounds_like","some","v","undefined","clear"],"mappings":"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"}
1
+ {"version":3,"file":"storage.js","sources":["../../src/context/storage.ts"],"sourcesContent":["/**\n * Context Storage\n * \n * Handles loading and saving entity YAML files from context directories.\n * Supports hierarchical loading where later directories override earlier ones.\n * \n * Design Note: This module is designed to be self-contained and may be\n * extracted for use in other tools (kronologi, observasjon) in the future.\n */\n\nimport * as yaml from 'js-yaml';\nimport * as fs from 'fs/promises';\n// eslint-disable-next-line no-restricted-imports\nimport { existsSync, statSync } from 'fs';\nimport * as path from 'node:path';\nimport { Entity, EntityType } from './types';\n\nexport interface StorageInstance {\n load(contextDirs: string[]): Promise<void>;\n save(entity: Entity, targetDir: string): Promise<void>;\n delete(type: EntityType, id: string, targetDir: string): Promise<boolean>;\n get<T extends Entity>(type: EntityType, id: string): T | undefined;\n getAll<T extends Entity>(type: EntityType): T[];\n search(query: string): Entity[];\n findBySoundsLike(phonetic: string): Entity | undefined;\n clear(): void;\n getEntityFilePath(type: EntityType, id: string, contextDirs: string[]): string | undefined;\n}\n\ntype DirectoryName = 'people' | 'projects' | 'companies' | 'terms' | 'ignored';\n\nconst DIRECTORY_TO_TYPE: Record<DirectoryName, EntityType> = {\n 'people': 'person',\n 'projects': 'project',\n 'companies': 'company',\n 'terms': 'term',\n 'ignored': 'ignored',\n};\n\nconst TYPE_TO_DIRECTORY: Record<EntityType, DirectoryName> = {\n 'person': 'people',\n 'project': 'projects',\n 'company': 'companies',\n 'term': 'terms',\n 'ignored': 'ignored',\n};\n\nexport const create = (): StorageInstance => {\n const entities: Map<EntityType, Map<string, Entity>> = new Map([\n ['person', new Map()],\n ['project', new Map()],\n ['company', new Map()],\n ['term', new Map()],\n ['ignored', new Map()],\n ]);\n\n const load = async (contextDirs: string[]): Promise<void> => {\n // Load from all context directories (later directories override)\n for (const contextDir of contextDirs) {\n for (const dirName of Object.keys(DIRECTORY_TO_TYPE) as DirectoryName[]) {\n const typeDir = path.join(contextDir, dirName);\n const entityType = DIRECTORY_TO_TYPE[dirName];\n \n try {\n const files = await fs.readdir(typeDir);\n for (const file of files) {\n if (!file.endsWith('.yaml') && !file.endsWith('.yml')) continue;\n \n const content = await fs.readFile(path.join(typeDir, file), 'utf-8');\n const parsed = yaml.load(content) as Partial<Entity>;\n \n if (parsed && parsed.id) {\n entities.get(entityType)?.set(parsed.id, {\n ...parsed,\n type: entityType,\n } as Entity);\n }\n }\n } catch {\n // Directory doesn't exist, skip\n }\n }\n }\n };\n\n const save = async (entity: Entity, targetDir: string): Promise<void> => {\n const dirName = TYPE_TO_DIRECTORY[entity.type];\n // Save to context subdirectory (context/people/, context/projects/, etc.)\n const dirPath = path.join(targetDir, 'context', dirName);\n await fs.mkdir(dirPath, { recursive: true });\n \n const filePath = path.join(dirPath, `${entity.id}.yaml`);\n \n // Remove type from saved YAML (it's inferred from directory)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { type: _entityType, ...entityWithoutType } = entity;\n const content = yaml.dump(entityWithoutType, { lineWidth: -1 });\n await fs.writeFile(filePath, content, 'utf-8');\n \n entities.get(entity.type)?.set(entity.id, entity);\n };\n\n const deleteEntity = async (type: EntityType, id: string, targetDir: string): Promise<boolean> => {\n const dirName = TYPE_TO_DIRECTORY[type];\n \n // Try both possible locations (with and without 'context' subdirectory)\n const possiblePaths = [\n path.join(targetDir, dirName, `${id}.yaml`),\n path.join(targetDir, dirName, `${id}.yml`),\n path.join(targetDir, 'context', dirName, `${id}.yaml`),\n path.join(targetDir, 'context', dirName, `${id}.yml`),\n ];\n \n for (const filePath of possiblePaths) {\n try {\n await fs.unlink(filePath);\n entities.get(type)?.delete(id);\n return true;\n } catch {\n // File doesn't exist at this path, try next\n }\n }\n \n return false;\n };\n\n const getEntityFilePath = (type: EntityType, id: string, contextDirs: string[]): string | undefined => {\n const dirName = TYPE_TO_DIRECTORY[type];\n \n // Search in reverse order (closest first) to find where the entity is defined\n for (const contextDir of [...contextDirs].reverse()) {\n const possiblePaths = [\n path.join(contextDir, dirName, `${id}.yaml`),\n path.join(contextDir, dirName, `${id}.yml`),\n ];\n \n for (const filePath of possiblePaths) {\n // Use sync access check - this is only for CLI, not hot path\n if (existsSync(filePath)) {\n const stat = statSync(filePath);\n if (stat.isFile()) {\n return filePath;\n }\n }\n }\n }\n \n return undefined;\n };\n\n const get = <T extends Entity>(type: EntityType, id: string): T | undefined => {\n return entities.get(type)?.get(id) as T | undefined;\n };\n\n const getAll = <T extends Entity>(type: EntityType): T[] => {\n return Array.from(entities.get(type)?.values() ?? []) as T[];\n };\n\n const search = (query: string): Entity[] => {\n const normalizedQuery = query.toLowerCase();\n const results: Entity[] = [];\n \n for (const entityMap of entities.values()) {\n for (const entity of entityMap.values()) {\n if (entity.name.toLowerCase().includes(normalizedQuery)) {\n results.push(entity);\n }\n }\n }\n \n return results;\n };\n\n const findBySoundsLike = (phonetic: string): Entity | undefined => {\n const normalized = phonetic.toLowerCase().trim();\n \n for (const entityMap of entities.values()) {\n for (const entity of entityMap.values()) {\n // Check sounds_like field on entities that have it\n const entityWithSoundsLike = entity as Entity & { sounds_like?: string[] };\n const variants = entityWithSoundsLike.sounds_like;\n if (variants?.some(v => v.toLowerCase() === normalized)) {\n return entity;\n }\n }\n }\n \n return undefined;\n };\n\n const clear = (): void => {\n for (const entityMap of entities.values()) {\n entityMap.clear();\n }\n };\n\n return { load, save, delete: deleteEntity, get, getAll, search, findBySoundsLike, clear, getEntityFilePath };\n};\n\n"],"names":["DIRECTORY_TO_TYPE","TYPE_TO_DIRECTORY","create","entities","Map","load","contextDirs","contextDir","dirName","Object","keys","typeDir","path","join","entityType","files","fs","readdir","file","endsWith","content","readFile","parsed","yaml","id","get","set","type","save","entity","targetDir","dirPath","mkdir","recursive","filePath","_entityType","entityWithoutType","dump","lineWidth","writeFile","deleteEntity","possiblePaths","unlink","delete","getEntityFilePath","reverse","existsSync","stat","statSync","isFile","undefined","getAll","Array","from","values","search","query","normalizedQuery","toLowerCase","results","entityMap","name","includes","push","findBySoundsLike","phonetic","normalized","trim","entityWithSoundsLike","variants","sounds_like","some","v","clear"],"mappings":"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"}
@@ -1,10 +1,22 @@
1
1
  import * as readline from 'readline';
2
2
  import { getLogger } from '../logging.js';
3
+ import { create as create$1 } from '../util/sound.js';
3
4
 
4
5
  const createReadlineInterface = ()=>{
6
+ // Ensure stdin is in the correct mode for readline
7
+ // This helps prevent issues with some terminal emulators
8
+ if (process.stdin.setRawMode) {
9
+ try {
10
+ // Ensure we're NOT in raw mode - readline handles this itself
11
+ process.stdin.setRawMode(false);
12
+ } catch {
13
+ // Ignore errors - some environments don't support setRawMode
14
+ }
15
+ }
5
16
  return readline.createInterface({
6
17
  input: process.stdin,
7
- output: process.stdout
18
+ output: process.stdout,
19
+ terminal: true
8
20
  });
9
21
  };
10
22
  const askQuestion = (rl, question)=>{
@@ -14,6 +26,224 @@ const askQuestion = (rl, question)=>{
14
26
  });
15
27
  });
16
28
  };
29
+ // Helper to write to stdout without triggering no-console lint rule
30
+ const write = (text)=>process.stdout.write(text + '\n');
31
+ // Simplified project creation (used when creating project from term/person association)
32
+ const runCreateProjectFlow = async (rl, contextMessage)=>{
33
+ if (contextMessage) {
34
+ write('');
35
+ write(contextMessage);
36
+ }
37
+ // Step 1: Project name (required)
38
+ const projectName = await askQuestion(rl, '\nProject name: ');
39
+ if (!projectName) {
40
+ write('Project name is required. Skipping project creation.');
41
+ return {
42
+ action: 'skip'
43
+ };
44
+ }
45
+ // Step 2: Destination
46
+ const destination = await askQuestion(rl, '\nWhere should output be routed to? (Enter for default): ');
47
+ // Step 3: Description
48
+ const description = await askQuestion(rl, '\nCan you tell me something about this project? (Enter to skip): ');
49
+ return {
50
+ action: 'create',
51
+ projectName: projectName.trim(),
52
+ destination: destination || undefined,
53
+ description: description || undefined
54
+ };
55
+ };
56
+ const runNewProjectWizard = async (rl, term, context, projectOptions)=>{
57
+ write('');
58
+ write('─'.repeat(60));
59
+ write(`[Unknown Project/Term]`);
60
+ write(`Term: "${term}"`);
61
+ write('');
62
+ if (context) {
63
+ // Display context with proper formatting (it now includes file info)
64
+ write(context);
65
+ }
66
+ write('─'.repeat(60));
67
+ // Step 1: Is this a project or a term?
68
+ const entityType = await askQuestion(rl, '\nIs this a Project or a Term? (P/T/X to ignore, or Enter to skip): ');
69
+ if (entityType === '' || entityType.toLowerCase() === 's' || entityType.toLowerCase() === 'skip') {
70
+ return {
71
+ action: 'skip'
72
+ };
73
+ }
74
+ // IGNORE FLOW - user doesn't want to be asked about this term again
75
+ if (entityType.toLowerCase() === 'x' || entityType.toLowerCase() === 'i' || entityType.toLowerCase() === 'ignore') {
76
+ write(`\n[Adding "${term}" to ignore list - you won't be asked about this again]`);
77
+ return {
78
+ action: 'ignore',
79
+ ignoredTerm: term
80
+ };
81
+ }
82
+ // PROJECT FLOW
83
+ if (entityType.toLowerCase() === 'p' || entityType.toLowerCase() === 'project') {
84
+ // Step 2: Project name
85
+ const projectName = await askQuestion(rl, `\nWhat is this project's name? [${term}]: `);
86
+ const finalName = projectName || term;
87
+ // Step 3: Destination
88
+ const destination = await askQuestion(rl, '\nWhere should output be routed to? (Enter for default): ');
89
+ // Step 4: Description
90
+ const description = await askQuestion(rl, '\nCan you tell me something about this project? (Enter to skip): ');
91
+ return {
92
+ action: 'create',
93
+ projectName: finalName,
94
+ destination: destination || undefined,
95
+ description: description || undefined
96
+ };
97
+ }
98
+ // TERM FLOW
99
+ if (entityType.toLowerCase() === 't' || entityType.toLowerCase() === 'term') {
100
+ // Step 2: Validate spelling
101
+ const termCorrection = await askQuestion(rl, `\nIs "${term}" spelled correctly? (Enter to accept, or type correction): `);
102
+ const finalTermName = termCorrection || term;
103
+ if (termCorrection) {
104
+ write(`Term updated to: "${finalTermName}"`);
105
+ }
106
+ // Step 3: Is this an acronym?
107
+ const expansion = await askQuestion(rl, `\nIf "${finalTermName}" is an acronym, what does it stand for? (Enter to skip): `);
108
+ // Step 4: Which project(s) is this term associated with?
109
+ const termProjects = [];
110
+ let createdProject;
111
+ if (projectOptions && projectOptions.length > 0) {
112
+ write('\nExisting projects:');
113
+ projectOptions.forEach((opt, i)=>{
114
+ write(` ${i + 1}. ${opt}`);
115
+ });
116
+ write(` N. Create a new project`);
117
+ const projectSelection = await askQuestion(rl, `\nWhich project(s) is "${finalTermName}" associated with? (Enter numbers separated by commas, N for new, or Enter to skip): `);
118
+ if (projectSelection.toLowerCase().includes('n')) {
119
+ // User wants to create a new project to associate with this term
120
+ write('');
121
+ write(`[Create New Project for Term "${finalTermName}"]`);
122
+ createdProject = await runCreateProjectFlow(rl, `The term "${finalTermName}" will be associated with this new project.`);
123
+ if (createdProject.action === 'create' && createdProject.projectName) {
124
+ write(`\n[Project "${createdProject.projectName}" will be created and associated with term "${finalTermName}"]`);
125
+ }
126
+ } else if (projectSelection) {
127
+ const indices = projectSelection.split(',').map((s)=>parseInt(s.trim(), 10) - 1);
128
+ for (const idx of indices){
129
+ if (!isNaN(idx) && idx >= 0 && idx < projectOptions.length) {
130
+ termProjects.push(idx);
131
+ }
132
+ }
133
+ if (termProjects.length > 0) {
134
+ write(`Associated with: ${termProjects.map((i)=>projectOptions[i].split(' - ')[0]).join(', ')}`);
135
+ }
136
+ }
137
+ } else {
138
+ // No existing projects - offer to create one
139
+ const createNew = await askQuestion(rl, `\nNo existing projects found. Create a new project for term "${finalTermName}"? (Y/N, or Enter to skip): `);
140
+ if (createNew.toLowerCase() === 'y' || createNew.toLowerCase() === 'yes') {
141
+ write('');
142
+ write(`[Create New Project for Term "${finalTermName}"]`);
143
+ createdProject = await runCreateProjectFlow(rl, `The term "${finalTermName}" will be associated with this new project.`);
144
+ if (createdProject.action === 'create' && createdProject.projectName) {
145
+ write(`\n[Project "${createdProject.projectName}" will be created and associated with term "${finalTermName}"]`);
146
+ }
147
+ }
148
+ }
149
+ // Step 5: Description
150
+ const termDesc = await askQuestion(rl, `\nBrief description of "${finalTermName}"? (Enter to skip): `);
151
+ return {
152
+ action: 'term',
153
+ termName: finalTermName,
154
+ termExpansion: expansion || undefined,
155
+ termProjects: termProjects.length > 0 ? termProjects : undefined,
156
+ termDescription: termDesc || undefined,
157
+ createdProject
158
+ };
159
+ }
160
+ // Unrecognized input
161
+ write('\nUnrecognized input. Please enter P for Project, T for Term, or press Enter to skip.');
162
+ return {
163
+ action: 'skip'
164
+ };
165
+ };
166
+ const runNewPersonWizard = async (rl, name, context, projectOptions)=>{
167
+ write('');
168
+ write('─'.repeat(60));
169
+ write(`[Unknown Person Detected]`);
170
+ write(`Name heard: "${name}"`);
171
+ write('');
172
+ if (context) {
173
+ // Display context with proper formatting (it now includes file info)
174
+ write(context);
175
+ }
176
+ write('─'.repeat(60));
177
+ // Step 1: Confirm name spelling
178
+ const nameCorrection = await askQuestion(rl, `\nIs the name spelled correctly? (Enter to accept, or type correction): `);
179
+ const finalName = nameCorrection || name;
180
+ if (nameCorrection) {
181
+ write(`Name updated to: "${finalName}"`);
182
+ }
183
+ // Step 2: Ask for organization/company
184
+ const organization = await askQuestion(rl, `\nWhat organization/company is ${finalName} with? (Enter to skip): `);
185
+ // Step 3: Project association
186
+ let linkedProjectIndex;
187
+ let createdProject;
188
+ // Show project options with "N" for new project
189
+ if (projectOptions && projectOptions.length > 0) {
190
+ write('\nExisting projects:');
191
+ projectOptions.forEach((opt, i)=>{
192
+ write(` ${i + 1}. ${opt}`);
193
+ });
194
+ write(` N. Create a new project`);
195
+ const projectSelection = await askQuestion(rl, `\nWhich project is ${finalName} related to? (Enter number, N for new, or Enter to skip): `);
196
+ if (projectSelection.toLowerCase() === 'n') {
197
+ // User wants to create a new project for this person
198
+ write('');
199
+ write(`[Create New Project for ${finalName}]`);
200
+ const contextMsg = organization ? `Creating project for ${finalName} (${organization})` : `Creating project for ${finalName}`;
201
+ createdProject = await runCreateProjectFlow(rl, contextMsg);
202
+ if (createdProject.action === 'create' && createdProject.projectName) {
203
+ write(`\n[Project "${createdProject.projectName}" will be created and linked to ${finalName}]`);
204
+ }
205
+ } else if (projectSelection && /^\d+$/.test(projectSelection)) {
206
+ const idx = parseInt(projectSelection, 10) - 1;
207
+ if (idx >= 0 && idx < projectOptions.length) {
208
+ linkedProjectIndex = idx;
209
+ write(`Linked to: ${projectOptions[idx]}`);
210
+ }
211
+ }
212
+ } else {
213
+ // No existing projects - offer to create one
214
+ const createNew = await askQuestion(rl, `\nNo existing projects found. Create a new project for ${finalName}? (Y/N, or Enter to skip): `);
215
+ if (createNew.toLowerCase() === 'y' || createNew.toLowerCase() === 'yes') {
216
+ write('');
217
+ write(`[Create New Project for ${finalName}]`);
218
+ const contextMsg = organization ? `Creating project for ${finalName} (${organization})` : `Creating project for ${finalName}`;
219
+ createdProject = await runCreateProjectFlow(rl, contextMsg);
220
+ if (createdProject.action === 'create' && createdProject.projectName) {
221
+ write(`\n[Project "${createdProject.projectName}" will be created and linked to ${finalName}]`);
222
+ }
223
+ }
224
+ }
225
+ // Step 4: Ask for notes about the person
226
+ const notes = await askQuestion(rl, `\nAny notes about ${finalName}? (Enter to skip): `);
227
+ // Determine if we should create the person
228
+ const hasInfo = organization || linkedProjectIndex !== undefined || createdProject || notes;
229
+ if (!hasInfo) {
230
+ // User skipped everything - confirm if they want to skip entirely
231
+ const confirm = await askQuestion(rl, `\nNo information provided. Skip saving ${finalName}? (Enter to skip, or any key to save anyway): `);
232
+ if (confirm === '') {
233
+ return {
234
+ action: 'skip'
235
+ };
236
+ }
237
+ }
238
+ return {
239
+ action: 'create',
240
+ personName: finalName,
241
+ organization: organization || undefined,
242
+ linkedProjectIndex,
243
+ notes: notes || undefined,
244
+ createdProject
245
+ };
246
+ };
17
247
  const formatClarificationPrompt = (request)=>{
18
248
  const lines = [];
19
249
  lines.push('');
@@ -37,11 +267,14 @@ const formatClarificationPrompt = (request)=>{
37
267
  lines.push('Who is this person? (brief description, or press Enter to skip):');
38
268
  break;
39
269
  case 'new_project':
40
- lines.push(`[New Project Detected]`);
41
- lines.push(`Context: ${request.context}`);
42
- lines.push(`Project name: "${request.term}"`);
270
+ // This case is handled by the wizard, but provide fallback prompt
271
+ lines.push(`[Unknown Project/Term]`);
272
+ lines.push(`Term: "${request.term}"`);
273
+ if (request.context) {
274
+ lines.push(`${request.context}`);
275
+ }
43
276
  lines.push('');
44
- lines.push('What is this project? (brief description, or press Enter to skip):');
277
+ lines.push('Is this a new project? (Y/N, or Enter to skip):');
45
278
  break;
46
279
  case 'new_company':
47
280
  lines.push(`[New Company Detected]`);
@@ -114,7 +347,11 @@ const formatClarificationPrompt = (request)=>{
114
347
  return lines.join('\n') + '\n> ';
115
348
  };
116
349
  const create = (config)=>{
350
+ var _config_silent;
117
351
  const logger = getLogger();
352
+ const sound = create$1({
353
+ silent: (_config_silent = config.silent) !== null && _config_silent !== void 0 ? _config_silent : false
354
+ });
118
355
  let session = null;
119
356
  let rl = null;
120
357
  const startSession = ()=>{
@@ -123,11 +360,23 @@ const create = (config)=>{
123
360
  responses: [],
124
361
  startedAt: new Date()
125
362
  };
126
- if (config.enabled) {
127
- rl = createReadlineInterface();
128
- logger.info('Interactive session started - will prompt for clarifications');
363
+ // Check if we can run interactively:
364
+ // 1. Interactive mode must be enabled (not --batch)
365
+ // 2. stdin must be a TTY (not piped/cron/etc)
366
+ const isTTY = process.stdin.isTTY === true;
367
+ if (config.enabled && isTTY) {
368
+ // Only create readline interface if one doesn't already exist
369
+ // This prevents duplicate input handlers when processing multiple files
370
+ if (!rl) {
371
+ rl = createReadlineInterface();
372
+ logger.info('Interactive session started - will prompt for clarifications');
373
+ } else {
374
+ logger.debug('Interactive session continued (readline already active)');
375
+ }
376
+ } else if (config.enabled && !isTTY) {
377
+ logger.info('Interactive mode enabled but stdin is not a TTY - running in auto-resolve mode');
129
378
  } else {
130
- logger.debug('Interactive session started (non-interactive mode)');
379
+ logger.debug('Interactive session started (batch mode)');
131
380
  }
132
381
  };
133
382
  const endSession = ()=>{
@@ -135,8 +384,17 @@ const create = (config)=>{
135
384
  throw new Error('No active session');
136
385
  }
137
386
  if (rl) {
387
+ // Remove all listeners before closing to prevent any lingering handlers
388
+ // Check if method exists (may not in mocks)
389
+ if (typeof rl.removeAllListeners === 'function') {
390
+ rl.removeAllListeners();
391
+ }
138
392
  rl.close();
139
393
  rl = null;
394
+ // Resume stdin in case it was paused
395
+ if (process.stdin.isPaused && process.stdin.isPaused()) {
396
+ process.stdin.resume();
397
+ }
140
398
  }
141
399
  session.completedAt = new Date();
142
400
  const completed = session;
@@ -170,6 +428,49 @@ const create = (config)=>{
170
428
  return response;
171
429
  }
172
430
  // Interactive mode - actually prompt the user
431
+ // Play notification sound to get user's attention (like Cursor does)
432
+ await sound.playNotification();
433
+ // Special handling for new_project - use wizard
434
+ if (request.type === 'new_project') {
435
+ const wizardResult = await runNewProjectWizard(rl, request.term, request.context, request.options);
436
+ const response = {
437
+ type: request.type,
438
+ term: request.term,
439
+ response: wizardResult.action,
440
+ shouldRemember: wizardResult.action !== 'skip',
441
+ additionalInfo: wizardResult
442
+ };
443
+ if (session) {
444
+ session.responses.push(response);
445
+ }
446
+ logger.debug('New project wizard completed', {
447
+ term: request.term,
448
+ action: wizardResult.action,
449
+ additionalInfo: wizardResult
450
+ });
451
+ return response;
452
+ }
453
+ // Special handling for new_person - use wizard
454
+ if (request.type === 'new_person') {
455
+ const wizardResult = await runNewPersonWizard(rl, request.term, request.context, request.options);
456
+ const response = {
457
+ type: request.type,
458
+ term: request.term,
459
+ response: wizardResult.action,
460
+ shouldRemember: wizardResult.action !== 'skip',
461
+ additionalInfo: wizardResult
462
+ };
463
+ if (session) {
464
+ session.responses.push(response);
465
+ }
466
+ logger.debug('New person wizard completed', {
467
+ term: request.term,
468
+ action: wizardResult.action,
469
+ additionalInfo: wizardResult
470
+ });
471
+ return response;
472
+ }
473
+ // Standard single-prompt flow for other types
173
474
  const prompt = formatClarificationPrompt(request);
174
475
  const userInput = await askQuestion(rl, prompt);
175
476
  // Process the user's response