opencode-traceability 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/commands/traceability-sync.md +20 -0
- package/.opencode/package.json +6 -0
- package/.opencode/plugins/traceability.ts +102 -0
- package/.opencode/skills/code-traceability/SKILL.md +39 -0
- package/README.md +135 -0
- package/bin/traceability.js +227 -0
- package/config/traceability.example.json +8 -0
- package/package.json +26 -0
- package/plugin.ts +56 -0
- package/scripts/enrich-codegraph.ps1 +98 -0
- package/scripts/traceability-sync.ps1 +74 -0
- package/scripts/traceability-sync.sh +39 -0
- package/scripts/validate-vault.ps1 +29 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Export Engram and enrich the shared vault with current CodeGraph callers.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Run the team traceability sync for the current workspace.
|
|
6
|
+
|
|
7
|
+
Requirements:
|
|
8
|
+
|
|
9
|
+
- `TRACEABILITY_VAULT`
|
|
10
|
+
- `TRACEABILITY_PROJECT`
|
|
11
|
+
- `TRACEABILITY_REPO_ROOT`
|
|
12
|
+
- `ENGRAM_BIN` or `engram` on PATH
|
|
13
|
+
|
|
14
|
+
Run:
|
|
15
|
+
|
|
16
|
+
```powershell
|
|
17
|
+
traceability sync --vault "$env:TRACEABILITY_VAULT" --project "$env:TRACEABILITY_PROJECT" --repo "$env:TRACEABILITY_REPO_ROOT"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Report the export, enrichment, reindex, and validation results. Do not modify application code.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { execFile } from "node:child_process"
|
|
2
|
+
import { promisify } from "node:util"
|
|
3
|
+
import { type Plugin, tool } from "@opencode-ai/plugin"
|
|
4
|
+
|
|
5
|
+
const execFileAsync = promisify(execFile)
|
|
6
|
+
type CommandResult = { stdout: string; stderr: string }
|
|
7
|
+
|
|
8
|
+
async function run(command: string, args: string[], env: NodeJS.ProcessEnv): Promise<CommandResult> {
|
|
9
|
+
return execFileAsync(command, args, {
|
|
10
|
+
env: { ...process.env, ...env },
|
|
11
|
+
windowsHide: true,
|
|
12
|
+
maxBuffer: 256 * 1024,
|
|
13
|
+
}) as Promise<CommandResult>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function required(name: string): string {
|
|
17
|
+
const value = process.env[name]
|
|
18
|
+
if (!value) throw new Error(`Missing required environment variable: ${name}`)
|
|
19
|
+
return value
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function optional(label: string, action: () => Promise<CommandResult>): Promise<string> {
|
|
23
|
+
try {
|
|
24
|
+
const result = await action()
|
|
25
|
+
return `### ${label}\n${result.stdout.trim() || result.stderr.trim() || "(sin resultados)"}`
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return `### ${label}\nUnavailable: ${String(error)}`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TraceabilityPlugin: Plugin = async ({ $, client }) => {
|
|
32
|
+
const autoSync = process.env.TRACEABILITY_AUTO_SYNC === "true"
|
|
33
|
+
const kitRoot = process.env.TRACEABILITY_KIT_ROOT
|
|
34
|
+
let syncInFlight: Promise<unknown> | undefined
|
|
35
|
+
|
|
36
|
+
const sync = async () => {
|
|
37
|
+
if (!autoSync || !kitRoot || syncInFlight) return
|
|
38
|
+
syncInFlight = (async () => {
|
|
39
|
+
try {
|
|
40
|
+
if (process.platform === "win32") {
|
|
41
|
+
await $`powershell -NoProfile -ExecutionPolicy Bypass -File ${kitRoot}/scripts/traceability-sync.ps1 -Enrich`
|
|
42
|
+
} else {
|
|
43
|
+
await $`${kitRoot}/scripts/traceability-sync.sh`
|
|
44
|
+
}
|
|
45
|
+
await client.app.log({ body: { service: "traceability", level: "info", message: "Automatic traceability sync completed" } })
|
|
46
|
+
} catch (error) {
|
|
47
|
+
await client.app.log({ body: { service: "traceability", level: "warn", message: `Automatic sync failed: ${String(error)}` } })
|
|
48
|
+
} finally {
|
|
49
|
+
syncInFlight = undefined
|
|
50
|
+
}
|
|
51
|
+
})()
|
|
52
|
+
await syncInFlight
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const contextTool = tool({
|
|
56
|
+
description: "Return bounded historical, vault, and CodeGraph context before changing a functionality.",
|
|
57
|
+
args: {
|
|
58
|
+
query: tool.schema.string().min(1).max(200).describe("Natural-language question or behavior to investigate"),
|
|
59
|
+
symbol: tool.schema.string().max(160).optional().describe("Optional exact CodeGraph symbol"),
|
|
60
|
+
limit: tool.schema.number().int().min(1).max(8).optional().describe("Maximum results per source"),
|
|
61
|
+
},
|
|
62
|
+
async execute(args) {
|
|
63
|
+
const vault = required("TRACEABILITY_VAULT")
|
|
64
|
+
const project = required("TRACEABILITY_PROJECT")
|
|
65
|
+
const repo = required("TRACEABILITY_REPO_ROOT")
|
|
66
|
+
const node = process.env.TRACEABILITY_NODE || "node"
|
|
67
|
+
const cli = process.env.OBSIDIAN_INTELLIGENCE_CLI || "vault-intelligence.js"
|
|
68
|
+
const engram = process.env.ENGRAM_BIN || "engram"
|
|
69
|
+
const codegraph = process.env.TRACEABILITY_CODEGRAPH_BIN || "codegraph"
|
|
70
|
+
const limit = String(args.limit || 5)
|
|
71
|
+
|
|
72
|
+
const parts = await Promise.all([
|
|
73
|
+
optional("Obsidian", () => run(node, [cli, "search", args.query, "--hybrid", "--limit", limit], { VAULT_PATH: vault })),
|
|
74
|
+
optional("Engram", () => run(engram, ["search", args.query, "--project", project, "--limit", limit], {})),
|
|
75
|
+
args.symbol
|
|
76
|
+
? optional("CodeGraph callers", () => run(codegraph, ["callers", args.symbol!, "--path", repo, "--limit", limit, "--json"], {}))
|
|
77
|
+
: Promise.resolve("### CodeGraph callers\nNo symbol supplied; caller lookup skipped."),
|
|
78
|
+
])
|
|
79
|
+
|
|
80
|
+
return [
|
|
81
|
+
"# Traceability context",
|
|
82
|
+
`Query: ${args.query}`,
|
|
83
|
+
args.symbol ? `Symbol: ${args.symbol}` : "",
|
|
84
|
+
...parts,
|
|
85
|
+
"Use this as evidence. Confirm current source with CodeGraph before editing.",
|
|
86
|
+
].filter(Boolean).join("\n\n")
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
tool: { traceability_context: contextTool },
|
|
92
|
+
event: async ({ event }: { event: { type?: string } }) => {
|
|
93
|
+
if (event.type === "session.idle") await sync()
|
|
94
|
+
},
|
|
95
|
+
"tool.execute.after": async (input: { tool?: string }) => {
|
|
96
|
+
if (input.tool && /sdd[-_]archive/i.test(input.tool)) await sync()
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { TraceabilityPlugin }
|
|
102
|
+
export default TraceabilityPlugin
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-traceability
|
|
3
|
+
description: Use before changing code or completing an SDD phase to connect current CodeGraph impact, Engram history, and the shared Markdown knowledge graph.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Team Code Traceability
|
|
7
|
+
|
|
8
|
+
## Before changing code
|
|
9
|
+
|
|
10
|
+
1. Resolve the repository root and project identity.
|
|
11
|
+
2. Search the shared vault with `hybrid_search` or `search_content` for the requested behavior.
|
|
12
|
+
3. Search Engram for the project and affected symbol/file.
|
|
13
|
+
4. Query CodeGraph for callers, callees, and affected files.
|
|
14
|
+
5. Compare historical decisions with the current tree. Report conflicts instead of guessing.
|
|
15
|
+
|
|
16
|
+
Use bounded context: include the relevant notes and up to the configured caller limit. Do not dump the complete vault into a prompt.
|
|
17
|
+
|
|
18
|
+
## During SDD
|
|
19
|
+
|
|
20
|
+
Persist phase artifacts under `sdd/<change>/<phase>` and preserve the user prompt/decision that produced them. Use Engram as operational memory; do not treat generated Markdown as the only source of truth.
|
|
21
|
+
|
|
22
|
+
## After archive or a significant change
|
|
23
|
+
|
|
24
|
+
1. Run the portable sync script with `TRACEABILITY_VAULT` and `TRACEABILITY_PROJECT`.
|
|
25
|
+
2. Run the CodeGraph enrichment script with `TRACEABILITY_REPO_ROOT`.
|
|
26
|
+
3. Update the corresponding Markdown node with:
|
|
27
|
+
- `Archivos que usan esta funcionalidad` from CodeGraph;
|
|
28
|
+
- `Historial de cambios sobre este nodo` from Engram;
|
|
29
|
+
- source IDs, dates, and links to the project MOC.
|
|
30
|
+
4. Reindex Obsidian Intelligence and validate broken links.
|
|
31
|
+
5. Keep the diff reviewable. Never rewrite unrelated notes.
|
|
32
|
+
|
|
33
|
+
## Safety
|
|
34
|
+
|
|
35
|
+
- Never modify application code automatically from this skill.
|
|
36
|
+
- Never use absolute paths belonging to another developer.
|
|
37
|
+
- Never commit credentials, SQLite databases, or CodeGraph indexes.
|
|
38
|
+
- Treat ambiguous or conflicting history as a human decision, not as permission to infer.
|
|
39
|
+
- Keep source files and Markdown projections separate and auditable.
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# OpenCode Traceability
|
|
2
|
+
|
|
3
|
+
Portable npm package that lets each developer investigate **their own** Engram memories and current repository with CodeGraph, then generate a local Obsidian-compatible knowledge graph.
|
|
4
|
+
|
|
5
|
+
The package contains no project memories, no vault notes, no SQLite database, and no credentials.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
From npm:
|
|
10
|
+
|
|
11
|
+
```powershell
|
|
12
|
+
npm install --global opencode-traceability
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For a local package build:
|
|
16
|
+
|
|
17
|
+
```powershell
|
|
18
|
+
npm install --global .\traceability-kit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Requirements:
|
|
22
|
+
|
|
23
|
+
- Node.js >= 18; npm installs `obsidian-intelligence` automatically.
|
|
24
|
+
- Engram CLI available as `engram` or through `ENGRAM_BIN`.
|
|
25
|
+
- CodeGraph initialized in the selected repository.
|
|
26
|
+
- OpenCode is optional for CLI-only use and required for the plugin.
|
|
27
|
+
|
|
28
|
+
## Create a project graph
|
|
29
|
+
|
|
30
|
+
The user chooses the project and vault. Nothing is imported until `sync` is executed.
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
traceability init `
|
|
34
|
+
--vault "C:\workspace\traceability-vault" `
|
|
35
|
+
--project "ta_schedule_backend"
|
|
36
|
+
|
|
37
|
+
traceability sync `
|
|
38
|
+
--vault "C:\workspace\traceability-vault" `
|
|
39
|
+
--project "ta_schedule_backend" `
|
|
40
|
+
--repo "C:\workspace\ta_schedule_backend"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`sync` performs:
|
|
44
|
+
|
|
45
|
+
1. `engram obsidian-export` for the selected Engram project.
|
|
46
|
+
2. CodeGraph caller enrichment for notes containing `codegraph_symbols`.
|
|
47
|
+
3. Obsidian Intelligence reindexing using the bundled npm dependency, or `OBSIDIAN_INTELLIGENCE_CLI` when overridden.
|
|
48
|
+
|
|
49
|
+
The generated graph belongs to the selected user/workspace. A different developer can select a different project and vault.
|
|
50
|
+
|
|
51
|
+
## Available commands
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
traceability init --vault <path> [--project <name>]
|
|
55
|
+
traceability sync --vault <path> --project <name> --repo <path> [--since <date>] [--force]
|
|
56
|
+
traceability watch --vault <path> --project <name> --repo <path> [--interval <minutes>]
|
|
57
|
+
traceability status --vault <path>
|
|
58
|
+
traceability graph --vault <path> [--limit <n>]
|
|
59
|
+
traceability context --query <text> [--symbol <name>] [--project <name>] [--repo <path>] [--vault <path>]
|
|
60
|
+
traceability opencode-install --repo <path>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Example investigation:
|
|
64
|
+
|
|
65
|
+
```powershell
|
|
66
|
+
traceability context `
|
|
67
|
+
--query "qué archivos usan el resolver de batches GTFS y qué cambios tuvo" `
|
|
68
|
+
--symbol "GtfsBatchResolver" `
|
|
69
|
+
--project "ta_schedule_backend" `
|
|
70
|
+
--repo "C:\workspace\ta_schedule_backend" `
|
|
71
|
+
--vault "C:\workspace\traceability-vault"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Continuous mode
|
|
75
|
+
|
|
76
|
+
```powershell
|
|
77
|
+
traceability watch `
|
|
78
|
+
--vault "C:\workspace\traceability-vault" `
|
|
79
|
+
--project "ta_schedule_backend" `
|
|
80
|
+
--repo "C:\workspace\ta_schedule_backend" `
|
|
81
|
+
--interval 10
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The watcher exports, enriches, and reindexes periodically. Stop with `Ctrl+C`.
|
|
85
|
+
|
|
86
|
+
## OpenCode integration
|
|
87
|
+
|
|
88
|
+
Install the npm plugin in the project's OpenCode configuration:
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
traceability opencode-install --repo "C:\workspace\ta_schedule_backend"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Restart OpenCode. The plugin provides `traceability_context` and triggers a bounded sync after an idle session or an `sdd-archive` tool event when:
|
|
95
|
+
|
|
96
|
+
```powershell
|
|
97
|
+
$env:TRACEABILITY_AUTO_SYNC = "true"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The plugin uses these environment variables:
|
|
101
|
+
|
|
102
|
+
```powershell
|
|
103
|
+
$env:TRACEABILITY_VAULT = "C:\workspace\traceability-vault"
|
|
104
|
+
$env:TRACEABILITY_PROJECT = "ta_schedule_backend"
|
|
105
|
+
$env:TRACEABILITY_REPO_ROOT = "C:\workspace\ta_schedule_backend"
|
|
106
|
+
$env:OBSIDIAN_INTELLIGENCE_CLI = "C:\workspace\node_modules\opencode-traceability\node_modules\obsidian-intelligence\vault-intelligence.js"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Team-sharing policy
|
|
110
|
+
|
|
111
|
+
Share the npm package or publish it to the team's private registry. Do not package or publish:
|
|
112
|
+
|
|
113
|
+
- Markdown notes from another developer's project;
|
|
114
|
+
- `.vault-intelligence.db` or other SQLite files;
|
|
115
|
+
- `.codegraph` indexes;
|
|
116
|
+
- `node_modules`;
|
|
117
|
+
- `.env`, tokens, credentials, or absolute machine paths.
|
|
118
|
+
|
|
119
|
+
The package is code only. Each developer creates or selects their own vault and Engram project at runtime.
|
|
120
|
+
|
|
121
|
+
## Source layout
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
bin/traceability.js # npm CLI
|
|
125
|
+
plugin.ts # OpenCode plugin entrypoint
|
|
126
|
+
scripts/ # sync, validation, and CodeGraph enrichment
|
|
127
|
+
config/ # portable configuration examples
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Safety
|
|
131
|
+
|
|
132
|
+
- The CLI never deletes notes or application code.
|
|
133
|
+
- Queries are bounded to eight results by default.
|
|
134
|
+
- The package treats Engram as operational memory and Markdown as the generated projection.
|
|
135
|
+
- Ambiguous project names must be resolved explicitly with `--project`.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs")
|
|
4
|
+
const os = require("node:os")
|
|
5
|
+
const path = require("node:path")
|
|
6
|
+
const { spawnSync } = require("node:child_process")
|
|
7
|
+
|
|
8
|
+
const packageRoot = path.resolve(__dirname, "..")
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const result = { _: [] }
|
|
12
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
13
|
+
const token = argv[index]
|
|
14
|
+
if (!token.startsWith("--")) {
|
|
15
|
+
result._.push(token)
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
const [key, inlineValue] = token.slice(2).split("=", 2)
|
|
19
|
+
if (inlineValue !== undefined) {
|
|
20
|
+
result[key] = inlineValue
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
const next = argv[index + 1]
|
|
24
|
+
if (!next || next.startsWith("--")) {
|
|
25
|
+
result[key] = true
|
|
26
|
+
continue
|
|
27
|
+
}
|
|
28
|
+
result[key] = next
|
|
29
|
+
index += 1
|
|
30
|
+
}
|
|
31
|
+
return result
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function usage() {
|
|
35
|
+
console.log(`Usage:
|
|
36
|
+
traceability init --vault <path> [--project <name>]
|
|
37
|
+
traceability sync --vault <path> --project <name> --repo <path> [--since <date>] [--force]
|
|
38
|
+
traceability watch --vault <path> --project <name> --repo <path> [--interval <minutes>]
|
|
39
|
+
traceability status --vault <path>
|
|
40
|
+
traceability graph --vault <path> [--limit <n>]
|
|
41
|
+
traceability context --query <text> [--symbol <name>] [--project <name>] [--repo <path>] [--vault <path>]
|
|
42
|
+
traceability opencode-install --repo <path>
|
|
43
|
+
|
|
44
|
+
Environment defaults:
|
|
45
|
+
TRACEABILITY_VAULT, TRACEABILITY_PROJECT, TRACEABILITY_REPO_ROOT,
|
|
46
|
+
TRACEABILITY_NODE, ENGRAM_BIN, TRACEABILITY_CODEGRAPH_BIN,
|
|
47
|
+
OBSIDIAN_INTELLIGENCE_CLI, TRACEABILITY_AUTO_SYNC`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function value(args, name, environmentName, required = false) {
|
|
51
|
+
const resolved = args[name] || process.env[environmentName]
|
|
52
|
+
if (required && !resolved) throw new Error(`Missing --${name} or ${environmentName}`)
|
|
53
|
+
return resolved
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function executable(name, environmentName) {
|
|
57
|
+
return process.env[environmentName] || name
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function run(command, args, options = {}) {
|
|
61
|
+
const result = spawnSync(command, args, {
|
|
62
|
+
cwd: options.cwd,
|
|
63
|
+
env: { ...process.env, ...(options.env || {}) },
|
|
64
|
+
encoding: "utf8",
|
|
65
|
+
stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
66
|
+
shell: process.platform === "win32",
|
|
67
|
+
})
|
|
68
|
+
if (result.error) throw result.error
|
|
69
|
+
if (result.status !== 0) {
|
|
70
|
+
throw new Error(`${command} exited with code ${result.status}`)
|
|
71
|
+
}
|
|
72
|
+
return options.capture ? { stdout: result.stdout || "", stderr: result.stderr || "" } : undefined
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function ensureDirectory(directory) {
|
|
76
|
+
fs.mkdirSync(directory, { recursive: true })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function writeIfMissing(file, content) {
|
|
80
|
+
if (!fs.existsSync(file)) fs.writeFileSync(file, content, "utf8")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function copyFilesIfMissing(sourceDirectory, destinationDirectory) {
|
|
84
|
+
ensureDirectory(destinationDirectory)
|
|
85
|
+
let copied = false
|
|
86
|
+
for (const entry of fs.readdirSync(sourceDirectory, { withFileTypes: true })) {
|
|
87
|
+
const source = path.join(sourceDirectory, entry.name)
|
|
88
|
+
const destination = path.join(destinationDirectory, entry.name)
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
copied = copyFilesIfMissing(source, destination) || copied
|
|
91
|
+
} else if (!fs.existsSync(destination)) {
|
|
92
|
+
fs.copyFileSync(source, destination)
|
|
93
|
+
copied = true
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return copied
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function init(args) {
|
|
100
|
+
const vault = value(args, "vault", "TRACEABILITY_VAULT", true)
|
|
101
|
+
const project = value(args, "project", "TRACEABILITY_PROJECT") || "knowledge-base"
|
|
102
|
+
ensureDirectory(vault)
|
|
103
|
+
writeIfMissing(path.join(vault, ".gitignore"), ".vault-intelligence.db\n*.db\n")
|
|
104
|
+
writeIfMissing(path.join(vault, "Inicio.md"), `---\ntype: moc\ntags: [inicio, moc]\n---\n\n# Bóveda de conocimiento\n\n- [[MOC - ${project}]]\n`)
|
|
105
|
+
writeIfMissing(path.join(vault, `MOC - ${project}.md`), `---\ntype: moc\ntags: [moc, ${project}]\n---\n\n# MOC — ${project}\n\n- [[Inicio]]\n\n## Cambios SDD\n\nLas notas se generan con el comando traceability sync.\n`)
|
|
106
|
+
console.log(`Vault initialized: ${vault}`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function exportMemories({ vault, project, since, force, watch = false, interval }) {
|
|
110
|
+
const args = ["obsidian-export", "--vault", vault, "--project", project]
|
|
111
|
+
if (since) args.push("--since", since)
|
|
112
|
+
if (force) args.push("--force")
|
|
113
|
+
if (watch) args.push("--watch", "--interval", `${interval}m`)
|
|
114
|
+
run(executable("engram", "ENGRAM_BIN"), args)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function enrich({ vault, repo, maxCallers }) {
|
|
118
|
+
const script = path.join(packageRoot, "scripts", "enrich-codegraph.ps1")
|
|
119
|
+
const environment = {
|
|
120
|
+
TRACEABILITY_VAULT: vault,
|
|
121
|
+
TRACEABILITY_REPO_ROOT: repo,
|
|
122
|
+
TRACEABILITY_CODEGRAPH_BIN: process.env.TRACEABILITY_CODEGRAPH_BIN || "codegraph",
|
|
123
|
+
}
|
|
124
|
+
if (process.platform === "win32") {
|
|
125
|
+
run("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script, "-MaxCallers", String(maxCallers || 8)], { env: environment })
|
|
126
|
+
} else {
|
|
127
|
+
console.warn("CodeGraph PowerShell enrichment is not available on POSIX yet; export completed.")
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function reindex(vault) {
|
|
132
|
+
const cli = process.env.OBSIDIAN_INTELLIGENCE_CLI || path.join(packageRoot, "node_modules", "obsidian-intelligence", "vault-intelligence.js")
|
|
133
|
+
if (!cli) {
|
|
134
|
+
console.warn("Reindex skipped. Set OBSIDIAN_INTELLIGENCE_CLI to vault-intelligence.js.")
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
const node = executable("node", "TRACEABILITY_NODE")
|
|
138
|
+
run(node, [cli, "index", "--vault", vault])
|
|
139
|
+
run(node, [cli, "status", "--vault", vault])
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function sync(args) {
|
|
143
|
+
const vault = value(args, "vault", "TRACEABILITY_VAULT", true)
|
|
144
|
+
const project = value(args, "project", "TRACEABILITY_PROJECT", true)
|
|
145
|
+
const repo = value(args, "repo", "TRACEABILITY_REPO_ROOT", true)
|
|
146
|
+
exportMemories({ vault, project, since: args.since, force: Boolean(args.force) })
|
|
147
|
+
enrich({ vault, repo, maxCallers: args.limit || 8 })
|
|
148
|
+
reindex(vault)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function watch(args) {
|
|
152
|
+
const interval = Number(args.interval || 10)
|
|
153
|
+
if (!Number.isFinite(interval) || interval < 1) throw new Error("--interval must be at least 1 minute")
|
|
154
|
+
const syncArgs = { ...args }
|
|
155
|
+
delete syncArgs._
|
|
156
|
+
sync(syncArgs)
|
|
157
|
+
console.log(`Watching ${value(args, "project", "TRACEABILITY_PROJECT", true)} every ${interval} minutes. Press Ctrl+C to stop.`)
|
|
158
|
+
setInterval(() => {
|
|
159
|
+
try { sync(syncArgs) } catch (error) { console.error(`[traceability] sync failed: ${error.message}`) }
|
|
160
|
+
}, interval * 60 * 1000)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function status(args) {
|
|
164
|
+
const vault = value(args, "vault", "TRACEABILITY_VAULT", true)
|
|
165
|
+
reindex(vault)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function graph(args) {
|
|
169
|
+
const vault = value(args, "vault", "TRACEABILITY_VAULT", true)
|
|
170
|
+
const cli = process.env.OBSIDIAN_INTELLIGENCE_CLI
|
|
171
|
+
if (!fs.existsSync(cli)) throw new Error("Obsidian Intelligence is not installed. Run npm install -g opencode-traceability or set OBSIDIAN_INTELLIGENCE_CLI.")
|
|
172
|
+
run(executable("node", "TRACEABILITY_NODE"), [cli, "graph", "hubs", String(args.limit || 10)], { env: { VAULT_PATH: vault } })
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function context(args) {
|
|
176
|
+
const query = value(args, "query", "TRACEABILITY_QUERY", true)
|
|
177
|
+
const vault = value(args, "vault", "TRACEABILITY_VAULT", true)
|
|
178
|
+
const project = value(args, "project", "TRACEABILITY_PROJECT", true)
|
|
179
|
+
const repo = value(args, "repo", "TRACEABILITY_REPO_ROOT")
|
|
180
|
+
const limit = String(args.limit || 5)
|
|
181
|
+
const cli = process.env.OBSIDIAN_INTELLIGENCE_CLI || path.join(packageRoot, "node_modules", "obsidian-intelligence", "vault-intelligence.js")
|
|
182
|
+
if (fs.existsSync(cli)) {
|
|
183
|
+
console.log("## Obsidian")
|
|
184
|
+
const result = run(executable("node", "TRACEABILITY_NODE"), [cli, "search", query, "--hybrid", "--limit", limit], { capture: true, env: { VAULT_PATH: vault } })
|
|
185
|
+
console.log(result.stdout.trim())
|
|
186
|
+
} else console.warn("Obsidian Intelligence unavailable; Engram and CodeGraph results will still be returned.")
|
|
187
|
+
console.log("## Engram")
|
|
188
|
+
const engram = run(executable("engram", "ENGRAM_BIN"), ["search", query, "--project", project, "--limit", limit], { capture: true })
|
|
189
|
+
console.log(engram.stdout.trim())
|
|
190
|
+
if (args.symbol && repo) {
|
|
191
|
+
console.log("## CodeGraph callers")
|
|
192
|
+
const callers = run(executable("codegraph", "TRACEABILITY_CODEGRAPH_BIN"), ["callers", args.symbol, "--path", repo, "--limit", limit, "--json"], { capture: true })
|
|
193
|
+
console.log(callers.stdout.trim())
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function installOpenCode(args) {
|
|
198
|
+
const repo = value(args, "repo", "TRACEABILITY_REPO_ROOT", true)
|
|
199
|
+
const configPath = path.join(repo, "opencode.json")
|
|
200
|
+
const config = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, "utf8")) : { $schema: "https://opencode.ai/config.json" }
|
|
201
|
+
const plugins = Array.isArray(config.plugin) ? config.plugin : []
|
|
202
|
+
if (!plugins.includes("opencode-traceability")) plugins.push("opencode-traceability")
|
|
203
|
+
config.plugin = plugins
|
|
204
|
+
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8")
|
|
205
|
+
const opencodeRoot = path.join(repo, ".opencode")
|
|
206
|
+
const skillSource = path.join(packageRoot, ".opencode", "skills")
|
|
207
|
+
const commandSource = path.join(packageRoot, ".opencode", "commands")
|
|
208
|
+
const skillInstalled = copyFilesIfMissing(skillSource, path.join(opencodeRoot, "skills"))
|
|
209
|
+
const commandInstalled = copyFilesIfMissing(commandSource, path.join(opencodeRoot, "commands"))
|
|
210
|
+
console.log(`Added opencode-traceability to ${configPath}. Skill copied: ${skillInstalled}. Command copied: ${commandInstalled}. Restart OpenCode.`)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function main() {
|
|
214
|
+
const args = parseArgs(process.argv.slice(2))
|
|
215
|
+
const command = args._[0]
|
|
216
|
+
if (!command || command === "help" || command === "--help") return usage()
|
|
217
|
+
if (command === "init") return init(args)
|
|
218
|
+
if (command === "sync") return sync(args)
|
|
219
|
+
if (command === "watch") return watch(args)
|
|
220
|
+
if (command === "status") return status(args)
|
|
221
|
+
if (command === "graph") return graph(args)
|
|
222
|
+
if (command === "context") return context(args)
|
|
223
|
+
if (command === "opencode-install") return installOpenCode(args)
|
|
224
|
+
throw new Error(`Unknown command: ${command}`)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try { main() } catch (error) { console.error(`[traceability] ${error.message}`); process.exitCode = 1 }
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-traceability",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"description": "Project-selectable CodeGraph, Engram, Obsidian, and OpenCode traceability toolkit",
|
|
6
|
+
"main": "plugin.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"traceability": "bin/traceability.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"lib",
|
|
13
|
+
"plugin.ts",
|
|
14
|
+
"scripts",
|
|
15
|
+
"README.md",
|
|
16
|
+
"config",
|
|
17
|
+
".opencode"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@opencode-ai/plugin": "1.18.31",
|
|
21
|
+
"obsidian-intelligence": "1.1.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/plugin.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { execFile } from "node:child_process"
|
|
2
|
+
import { promisify } from "node:util"
|
|
3
|
+
import { type Plugin, tool } from "@opencode-ai/plugin"
|
|
4
|
+
|
|
5
|
+
const execFileAsync = promisify(execFile)
|
|
6
|
+
|
|
7
|
+
const TraceabilityPlugin: Plugin = async ({ $, client }) => {
|
|
8
|
+
const syncEnabled = process.env.TRACEABILITY_AUTO_SYNC === "true"
|
|
9
|
+
const cli = process.env.TRACEABILITY_CLI || "traceability"
|
|
10
|
+
let running = false
|
|
11
|
+
|
|
12
|
+
const sync = async () => {
|
|
13
|
+
if (!syncEnabled || running) return
|
|
14
|
+
running = true
|
|
15
|
+
try {
|
|
16
|
+
await $`${cli} sync`
|
|
17
|
+
await client.app.log({ body: { service: "traceability", level: "info", message: "Traceability sync completed" } })
|
|
18
|
+
} catch (error) {
|
|
19
|
+
await client.app.log({ body: { service: "traceability", level: "warn", message: `Traceability sync failed: ${String(error)}` } })
|
|
20
|
+
} finally {
|
|
21
|
+
running = false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
tool: {
|
|
27
|
+
traceability_context: tool({
|
|
28
|
+
description: "Investigate bounded project context across Engram, Obsidian, and CodeGraph.",
|
|
29
|
+
args: {
|
|
30
|
+
query: tool.schema.string().min(1).max(200),
|
|
31
|
+
symbol: tool.schema.string().max(160).optional(),
|
|
32
|
+
limit: tool.schema.number().int().min(1).max(8).optional(),
|
|
33
|
+
},
|
|
34
|
+
async execute(args) {
|
|
35
|
+
const command = ["context", "--query", args.query]
|
|
36
|
+
if (args.symbol) command.push("--symbol", args.symbol)
|
|
37
|
+
if (args.limit) command.push("--limit", String(args.limit))
|
|
38
|
+
const result = await execFileAsync(cli, command, {
|
|
39
|
+
env: process.env,
|
|
40
|
+
shell: process.platform === "win32",
|
|
41
|
+
maxBuffer: 256 * 1024,
|
|
42
|
+
})
|
|
43
|
+
return result.stdout || result.stderr
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
event: async ({ event }) => {
|
|
48
|
+
if (event.type === "session.idle") await sync()
|
|
49
|
+
},
|
|
50
|
+
"tool.execute.after": async (input) => {
|
|
51
|
+
if (input.tool && /sdd[-_]archive/i.test(input.tool)) await sync()
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default TraceabilityPlugin
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[CmdletBinding()]
|
|
2
|
+
param(
|
|
3
|
+
[ValidateRange(1, 100)]
|
|
4
|
+
[int]$MaxCallers = 8,
|
|
5
|
+
[switch]$Strict
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
$ErrorActionPreference = "Stop"
|
|
9
|
+
|
|
10
|
+
function Require-Environment([string]$Name) {
|
|
11
|
+
$value = [Environment]::GetEnvironmentVariable($Name)
|
|
12
|
+
if ([string]::IsNullOrWhiteSpace($value)) { throw "Required environment variable is missing: $Name" }
|
|
13
|
+
return $value
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
$vault = Require-Environment "TRACEABILITY_VAULT"
|
|
17
|
+
$repo = Require-Environment "TRACEABILITY_REPO_ROOT"
|
|
18
|
+
if (-not (Test-Path -LiteralPath $vault -PathType Container)) { throw "Vault does not exist: $vault" }
|
|
19
|
+
if (-not (Test-Path -LiteralPath $repo -PathType Container)) { throw "Repository does not exist: $repo" }
|
|
20
|
+
|
|
21
|
+
$codegraph = $env:TRACEABILITY_CODEGRAPH_BIN
|
|
22
|
+
if ([string]::IsNullOrWhiteSpace($codegraph)) {
|
|
23
|
+
$command = Get-Command codegraph -ErrorAction SilentlyContinue
|
|
24
|
+
if ($null -eq $command) { throw "CodeGraph CLI not found. Set TRACEABILITY_CODEGRAPH_BIN or add codegraph to PATH." }
|
|
25
|
+
$codegraph = $command.Source
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function Get-Symbols([string]$Text) {
|
|
29
|
+
$match = [regex]::Match($Text, '(?m)^codegraph_symbols:\s*\[(.*?)\]\s*$')
|
|
30
|
+
if (-not $match.Success) { return @() }
|
|
31
|
+
return @($match.Groups[1].Value.Split(',') | ForEach-Object { $_.Trim().Trim('"', "'") } | Where-Object { $_ })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function Format-Consumers([hashtable]$Consumers, [string[]]$Symbols) {
|
|
35
|
+
$lines = [System.Collections.Generic.List[string]]::new()
|
|
36
|
+
if ($Consumers.Count -eq 0) {
|
|
37
|
+
$lines.Add('- Sin consumidores detectados en CodeGraph para los símbolos declarados.')
|
|
38
|
+
return $lines
|
|
39
|
+
}
|
|
40
|
+
foreach ($path in ($Consumers.Keys | Sort-Object)) {
|
|
41
|
+
$details = @($Consumers[$path] | Sort-Object -Unique) -join ', '
|
|
42
|
+
$lines.Add(('- `{0}` — caller de `{1}`' -f $path, $details))
|
|
43
|
+
}
|
|
44
|
+
return $lines
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
$updated = 0
|
|
48
|
+
$failed = 0
|
|
49
|
+
foreach ($file in Get-ChildItem -LiteralPath $vault -Filter *.md -File) {
|
|
50
|
+
$text = [System.IO.File]::ReadAllText($file.FullName, [System.Text.UTF8Encoding]::new($false))
|
|
51
|
+
$symbols = @(Get-Symbols $text)
|
|
52
|
+
if ($symbols.Count -eq 0) { continue }
|
|
53
|
+
|
|
54
|
+
$consumers = @{}
|
|
55
|
+
foreach ($symbol in $symbols) {
|
|
56
|
+
try {
|
|
57
|
+
$json = (& $codegraph callers $symbol --path $repo --limit $MaxCallers --json | Out-String) | ConvertFrom-Json
|
|
58
|
+
foreach ($caller in @($json.callers)) {
|
|
59
|
+
if (-not $caller.filePath) { continue }
|
|
60
|
+
if (-not $consumers.ContainsKey($caller.filePath)) { $consumers[$caller.filePath] = [System.Collections.Generic.List[string]]::new() }
|
|
61
|
+
$line = if ($caller.startLine) { ":$($caller.startLine)" } else { "" }
|
|
62
|
+
$consumers[$caller.filePath].Add("$symbol$line")
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
$failed++
|
|
66
|
+
Write-Warning ("CodeGraph failed for {0} in {1}: {2}" -f $symbol, $file.Name, $_.Exception.Message)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
$section = [System.Collections.Generic.List[string]]::new()
|
|
71
|
+
$section.Add("## Archivos que usan esta funcionalidad")
|
|
72
|
+
$section.Add("")
|
|
73
|
+
foreach ($line in (Format-Consumers $consumers $symbols)) { $section.Add($line) }
|
|
74
|
+
$section.Add("")
|
|
75
|
+
$newSection = $section -join [Environment]::NewLine
|
|
76
|
+
|
|
77
|
+
$heading = "## Archivos que usan esta funcionalidad"
|
|
78
|
+
$history = "## Historial de cambios sobre este nodo"
|
|
79
|
+
$sources = "## Fuentes"
|
|
80
|
+
$start = $text.IndexOf($heading, [System.StringComparison]::Ordinal)
|
|
81
|
+
if ($start -ge 0) {
|
|
82
|
+
$end = $text.IndexOf($history, $start, [System.StringComparison]::Ordinal)
|
|
83
|
+
if ($end -lt 0) { $end = $text.IndexOf($sources, $start, [System.StringComparison]::Ordinal) }
|
|
84
|
+
if ($end -lt 0) { $end = $text.Length }
|
|
85
|
+
$text = $text.Substring(0, $start) + $newSection + [Environment]::NewLine + [Environment]::NewLine + $text.Substring($end)
|
|
86
|
+
} else {
|
|
87
|
+
$insertAt = $text.IndexOf($history, [System.StringComparison]::Ordinal)
|
|
88
|
+
if ($insertAt -lt 0) { $insertAt = $text.IndexOf($sources, [System.StringComparison]::Ordinal) }
|
|
89
|
+
if ($insertAt -lt 0) { $insertAt = $text.Length }
|
|
90
|
+
$text = $text.Substring(0, $insertAt) + $newSection + [Environment]::NewLine + [Environment]::NewLine + $text.Substring($insertAt)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
[System.IO.File]::WriteAllText($file.FullName, $text, [System.Text.UTF8Encoding]::new($false))
|
|
94
|
+
$updated++
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Write-Output "CodeGraph enrichment complete: $updated notes updated, $failed symbol queries failed."
|
|
98
|
+
if ($Strict -and $failed -gt 0) { exit 1 }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[CmdletBinding()]
|
|
2
|
+
param(
|
|
3
|
+
[string]$Since,
|
|
4
|
+
[switch]$Force,
|
|
5
|
+
[switch]$Enrich,
|
|
6
|
+
[switch]$Watch,
|
|
7
|
+
[ValidateRange(1, 1440)]
|
|
8
|
+
[int]$IntervalMinutes = 10,
|
|
9
|
+
[switch]$DryRun
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
$ErrorActionPreference = "Stop"
|
|
13
|
+
|
|
14
|
+
function Require-Environment([string]$Name) {
|
|
15
|
+
$value = [Environment]::GetEnvironmentVariable($Name)
|
|
16
|
+
if ([string]::IsNullOrWhiteSpace($value)) {
|
|
17
|
+
throw "Required environment variable is missing: $Name"
|
|
18
|
+
}
|
|
19
|
+
return $value
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
$vault = Require-Environment "TRACEABILITY_VAULT"
|
|
23
|
+
$project = Require-Environment "TRACEABILITY_PROJECT"
|
|
24
|
+
if (-not (Test-Path -LiteralPath $vault -PathType Container)) { throw "Vault does not exist: $vault" }
|
|
25
|
+
|
|
26
|
+
$engram = $env:ENGRAM_BIN
|
|
27
|
+
if ([string]::IsNullOrWhiteSpace($engram)) {
|
|
28
|
+
$command = Get-Command engram -ErrorAction SilentlyContinue
|
|
29
|
+
if ($null -eq $command) { throw "Engram CLI not found. Set ENGRAM_BIN or add engram to PATH." }
|
|
30
|
+
$engram = $command.Source
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
$args = @("obsidian-export", "--vault", $vault, "--project", $project)
|
|
34
|
+
if ($Since) { $args += @("--since", $Since) }
|
|
35
|
+
if ($Force) { $args += "--force" }
|
|
36
|
+
if ($Watch) { $args += @("--watch", "--interval", ("{0}m" -f $IntervalMinutes)) }
|
|
37
|
+
|
|
38
|
+
if ($DryRun) {
|
|
39
|
+
Write-Output ("DRY RUN: {0} {1}" -f $engram, ($args -join " "))
|
|
40
|
+
exit 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
& $engram @args
|
|
44
|
+
if ($LASTEXITCODE -ne 0) { throw "Engram export failed with exit code $LASTEXITCODE" }
|
|
45
|
+
if ($Watch) { exit 0 }
|
|
46
|
+
|
|
47
|
+
if ($Enrich) {
|
|
48
|
+
$enricher = Join-Path $PSScriptRoot "enrich-codegraph.ps1"
|
|
49
|
+
& $enricher
|
|
50
|
+
if ($LASTEXITCODE -ne 0) { throw "CodeGraph enrichment failed with exit code $LASTEXITCODE" }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
$cli = $env:OBSIDIAN_INTELLIGENCE_CLI
|
|
54
|
+
if ([string]::IsNullOrWhiteSpace($cli)) {
|
|
55
|
+
$candidate = Join-Path $PSScriptRoot "..\node_modules\obsidian-intelligence\vault-intelligence.js"
|
|
56
|
+
if (Test-Path -LiteralPath $candidate) { $cli = (Resolve-Path -LiteralPath $candidate).Path }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if ([string]::IsNullOrWhiteSpace($cli) -or -not (Test-Path -LiteralPath $cli -PathType Leaf)) {
|
|
60
|
+
Write-Warning "Engram export completed; Obsidian reindex skipped. Set OBSIDIAN_INTELLIGENCE_CLI to vault-intelligence.js."
|
|
61
|
+
exit 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
$node = $env:TRACEABILITY_NODE
|
|
65
|
+
if ([string]::IsNullOrWhiteSpace($node)) {
|
|
66
|
+
$nodeCommand = Get-Command node -ErrorAction SilentlyContinue
|
|
67
|
+
if ($null -eq $nodeCommand) { throw "Node not found. Set TRACEABILITY_NODE." }
|
|
68
|
+
$node = $nodeCommand.Source
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
& $node $cli index --vault $vault
|
|
72
|
+
if ($LASTEXITCODE -ne 0) { throw "Obsidian index failed with exit code $LASTEXITCODE" }
|
|
73
|
+
& $node $cli status --vault $vault
|
|
74
|
+
if ($LASTEXITCODE -ne 0) { throw "Obsidian status failed with exit code $LASTEXITCODE" }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
: "${TRACEABILITY_VAULT:?Set TRACEABILITY_VAULT}"
|
|
5
|
+
: "${TRACEABILITY_PROJECT:?Set TRACEABILITY_PROJECT}"
|
|
6
|
+
|
|
7
|
+
ENGRAM_BIN="${ENGRAM_BIN:-$(command -v engram || true)}"
|
|
8
|
+
if [[ -z "$ENGRAM_BIN" ]]; then
|
|
9
|
+
echo "Engram CLI not found; set ENGRAM_BIN." >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
args=(obsidian-export --vault "$TRACEABILITY_VAULT" --project "$TRACEABILITY_PROJECT")
|
|
14
|
+
[[ -n "${TRACEABILITY_SINCE:-}" ]] && args+=(--since "$TRACEABILITY_SINCE")
|
|
15
|
+
[[ "${TRACEABILITY_FORCE:-false}" == "true" ]] && args+=(--force)
|
|
16
|
+
[[ "${TRACEABILITY_WATCH:-false}" == "true" ]] && args+=(--watch --interval "${TRACEABILITY_INTERVAL:-10m}")
|
|
17
|
+
|
|
18
|
+
if [[ "${TRACEABILITY_DRY_RUN:-false}" == "true" ]]; then
|
|
19
|
+
printf 'DRY RUN: %q ' "$ENGRAM_BIN" "${args[@]}"
|
|
20
|
+
printf '\n'
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
"$ENGRAM_BIN" "${args[@]}"
|
|
25
|
+
[[ "${TRACEABILITY_WATCH:-false}" == "true" ]] && exit 0
|
|
26
|
+
|
|
27
|
+
CLI="${OBSIDIAN_INTELLIGENCE_CLI:-}"
|
|
28
|
+
NODE_BIN="${TRACEABILITY_NODE:-node}"
|
|
29
|
+
if [[ -z "$CLI" ]]; then
|
|
30
|
+
CLI="$PWD/node_modules/obsidian-intelligence/vault-intelligence.js"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
if [[ ! -f "$CLI" ]]; then
|
|
34
|
+
echo "Engram export completed; Obsidian reindex skipped. Set OBSIDIAN_INTELLIGENCE_CLI." >&2
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
"$NODE_BIN" "$CLI" index --vault "$TRACEABILITY_VAULT"
|
|
39
|
+
"$NODE_BIN" "$CLI" status --vault "$TRACEABILITY_VAULT"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[CmdletBinding()]
|
|
2
|
+
param([switch]$Strict)
|
|
3
|
+
|
|
4
|
+
$ErrorActionPreference = "Stop"
|
|
5
|
+
$errors = [System.Collections.Generic.List[string]]::new()
|
|
6
|
+
|
|
7
|
+
if ([string]::IsNullOrWhiteSpace($env:TRACEABILITY_VAULT)) { $errors.Add("TRACEABILITY_VAULT is missing") }
|
|
8
|
+
elseif (-not (Test-Path -LiteralPath $env:TRACEABILITY_VAULT -PathType Container)) { $errors.Add("Vault does not exist: $env:TRACEABILITY_VAULT") }
|
|
9
|
+
|
|
10
|
+
if ([string]::IsNullOrWhiteSpace($env:TRACEABILITY_PROJECT)) { $errors.Add("TRACEABILITY_PROJECT is missing") }
|
|
11
|
+
if ([string]::IsNullOrWhiteSpace($env:TRACEABILITY_REPO_ROOT)) { $errors.Add("TRACEABILITY_REPO_ROOT is missing") }
|
|
12
|
+
|
|
13
|
+
$engram = if ($env:ENGRAM_BIN) { $env:ENGRAM_BIN } else { (Get-Command engram -ErrorAction SilentlyContinue).Source }
|
|
14
|
+
if ([string]::IsNullOrWhiteSpace($engram)) { $errors.Add("Engram CLI not found") }
|
|
15
|
+
|
|
16
|
+
if ($errors.Count -gt 0) {
|
|
17
|
+
$errors | ForEach-Object { Write-Error $_ }
|
|
18
|
+
if ($Strict) { exit 1 }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if ($env:TRACEABILITY_VAULT -and (Test-Path -LiteralPath $env:TRACEABILITY_VAULT)) {
|
|
22
|
+
$notes = @(Get-ChildItem -LiteralPath $env:TRACEABILITY_VAULT -Filter *.md -File)
|
|
23
|
+
Write-Output "Vault: $env:TRACEABILITY_VAULT"
|
|
24
|
+
Write-Output "Markdown notes: $($notes.Count)"
|
|
25
|
+
Write-Output "SQLite indexes are local and must remain ignored by Git."
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if ($errors.Count -gt 0) { exit 1 }
|
|
29
|
+
Write-Output "Traceability configuration is valid."
|