@cairnkeep/cli 1.0.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.
- package/AGENTS.md +45 -0
- package/CONTRIBUTING.md +49 -0
- package/LICENSE +201 -0
- package/README.md +149 -0
- package/SECURITY.md +37 -0
- package/bin/cairn +73 -0
- package/claude/agents/code-reviewer.md +174 -0
- package/claude/agents/security-investigator.md +112 -0
- package/claude/agents/security-target-selector.md +90 -0
- package/claude/agents/security-validator.md +97 -0
- package/claude/agents/wiki-ingester.md +108 -0
- package/claude/agents/wiki-lint-auditor.md +94 -0
- package/claude/agents/wiki-query-analyst.md +84 -0
- package/claude/commands/context-explore.md +40 -0
- package/claude/commands/graphify.md +150 -0
- package/claude/commands/memory-review.md +85 -0
- package/claude/commands/memory-sync.md +71 -0
- package/claude/commands/recall.md +32 -0
- package/claude/commands/remember.md +43 -0
- package/claude/commands/repo-review.md +191 -0
- package/claude/commands/security-audit.md +174 -0
- package/claude/commands/wiki-ingest.md +112 -0
- package/claude/commands/wiki-lint.md +86 -0
- package/claude/commands/wiki-query.md +92 -0
- package/claude/hooks/context-explore-pretask.sh +93 -0
- package/claude/hooks/memory-capture.sh +59 -0
- package/claude/hooks/memory-recall.sh +84 -0
- package/claude/hooks/memory-wakeup.sh +45 -0
- package/docs/building-an-overlay.md +117 -0
- package/docs/domain-knowledge.md +86 -0
- package/docs/git-providers.md +36 -0
- package/docs/operating.md +366 -0
- package/mcp-memory-server/dist/embeddings.js +122 -0
- package/mcp-memory-server/dist/explore-cache.js +97 -0
- package/mcp-memory-server/dist/index.js +1256 -0
- package/mcp-memory-server/package.json +36 -0
- package/opencode/agents/code-reviewer.md +173 -0
- package/opencode/agents/security-investigator.md +111 -0
- package/opencode/agents/security-target-selector.md +89 -0
- package/opencode/agents/security-validator.md +96 -0
- package/opencode/agents/wiki-ingester.md +107 -0
- package/opencode/agents/wiki-lint-auditor.md +93 -0
- package/opencode/agents/wiki-query-analyst.md +83 -0
- package/opencode/command/code-review.md +199 -0
- package/opencode/command/context-explore.md +42 -0
- package/opencode/command/graphify.md +153 -0
- package/opencode/command/memory-review.md +44 -0
- package/opencode/command/memory-sync.md +94 -0
- package/opencode/command/recall.md +38 -0
- package/opencode/command/remember.md +46 -0
- package/opencode/command/security-audit.md +80 -0
- package/opencode/command/wiki-ingest.md +55 -0
- package/opencode/command/wiki-lint.md +51 -0
- package/opencode/command/wiki-query.md +50 -0
- package/opencode/plugins/memory-capture.ts +188 -0
- package/opencode/plugins/memory-recall.ts +132 -0
- package/opencode/plugins/memory-wakeup.ts +102 -0
- package/opencode/templates/security-finding-register.yaml.template +51 -0
- package/opencode/templates/security-issue.md.template +43 -0
- package/opencode/templates/security-policy.md.template +29 -0
- package/opencode/templates/security-report.md.template +43 -0
- package/opencode/templates/wiki-contradictions.md.template +31 -0
- package/opencode/templates/wiki-index.md.template +40 -0
- package/opencode/templates/wiki-lint-report.md.template +38 -0
- package/opencode/templates/wiki-log.md.template +11 -0
- package/opencode/templates/wiki-policy.md.template +47 -0
- package/opencode/templates/wiki-query-answer.md.template +26 -0
- package/opencode/templates/wiki-source-summary.md.template +33 -0
- package/opencode/workflows/memory-review-workflow.md +66 -0
- package/opencode/workflows/security-audit-workflow.md +197 -0
- package/opencode/workflows/wiki-ingest-workflow.md +149 -0
- package/opencode/workflows/wiki-lint-workflow.md +128 -0
- package/opencode/workflows/wiki-query-workflow.md +122 -0
- package/package.json +56 -0
- package/scripts/bootstrap.sh +99 -0
- package/scripts/doctor.sh +91 -0
- package/scripts/install-audit-timer.sh +68 -0
- package/scripts/lib/assert-tool-event.mjs +50 -0
- package/scripts/memory-store.sh +71 -0
- package/scripts/memory-wiki-audit.sh +156 -0
- package/scripts/sync-claude-assets.sh +182 -0
- package/scripts/sync-opencode-explore-assets.sh +187 -0
- package/scripts/sync-opencode-graphify-assets.sh +152 -0
- package/scripts/sync-opencode-memory-assets.sh +160 -0
- package/scripts/sync-opencode-plugin-assets.sh +163 -0
- package/scripts/sync-opencode-security-assets.sh +195 -0
- package/scripts/sync-opencode-wiki-assets.sh +250 -0
- package/scripts/test-audit-timer.sh +29 -0
- package/scripts/test-bootstrap-untracked.sh +41 -0
- package/scripts/test-cli-dispatch.sh +21 -0
- package/scripts/test-doctor.sh +32 -0
- package/scripts/test-launcher-hooks.sh +66 -0
- package/scripts/test-memory-store.sh +44 -0
- package/scripts/test-remember-recall-assertions.sh +70 -0
- package/scripts/transcript-to-text.mjs +53 -0
- package/scripts/verify-docs-parity.sh +112 -0
- package/scripts/verify-explore-maturation.sh +288 -0
- package/scripts/verify-fastcontext-reliability.sh +624 -0
- package/scripts/verify-no-private-references.sh +145 -0
- package/scripts/verify-opencode-live-parity.sh +864 -0
- package/scripts/verify-routing-seam.sh +169 -0
- package/scripts/verify-token-savings-ab.sh +557 -0
- package/templates/PROJECT-INSTRUCTIONS.md.template +42 -0
- package/templates/alignment-gap-register.yaml.template +56 -0
- package/templates/alignment-policy.md.template +28 -0
- package/templates/alignment-pr-template.md.template +18 -0
- package/templates/alignment-report.md.template +56 -0
- package/templates/env.example.template +25 -0
- package/templates/graph-policy.md.template +37 -0
- package/templates/graphs-gitignore.template +3 -0
- package/templates/memory-wiki-audit.service.template +6 -0
- package/templates/memory-wiki-audit.timer.template +9 -0
- package/templates/memory.json.template +4 -0
- package/templates/planning-config.json.template +6 -0
- package/templates/project-brief.md.template +40 -0
- package/templates/security-finding-register.yaml.template +51 -0
- package/templates/security-issue.md.template +43 -0
- package/templates/security-policy.md.template +29 -0
- package/templates/security-report.md.template +43 -0
- package/templates/start-claude.sh.template +46 -0
- package/templates/start-opencode.sh.template +41 -0
- package/templates/structured-change-canvas.md.template +63 -0
- package/templates/wiki-contradictions.md.template +31 -0
- package/templates/wiki-index.md.template +40 -0
- package/templates/wiki-lint-report.md.template +38 -0
- package/templates/wiki-log.md.template +11 -0
- package/templates/wiki-policy.md.template +47 -0
- package/templates/wiki-query-answer.md.template +26 -0
- package/templates/wiki-source-summary.md.template +33 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin"
|
|
2
|
+
import fs from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
// OpenCode-native reimplementation of the Claude PreToolUse `memory-recall`
|
|
6
|
+
// hook (OCP-02, D-10). Before an edit/write proceeds, surface AgentFS facts
|
|
7
|
+
// and wiki pages that specifically mention the file about to be edited —
|
|
8
|
+
// high-signal / low-noise: inject nothing on routine edits, only on a
|
|
9
|
+
// specific stem match.
|
|
10
|
+
//
|
|
11
|
+
// Per 04-RESEARCH.md Pattern 2 / D-11, `tool.execute.before` cannot append
|
|
12
|
+
// freeform non-blocking context the way Claude's `additionalContext` does.
|
|
13
|
+
// The only confirmed mechanism is to `throw new Error(text)`, which the model
|
|
14
|
+
// sees as the tool call's result and must retry — this blocks the first
|
|
15
|
+
// attempt at a matched edit. A once-per-file-per-session guard prevents
|
|
16
|
+
// re-throwing (and re-blocking) the same file forever (T-04-09).
|
|
17
|
+
//
|
|
18
|
+
// Known OpenCode limitation (Pitfall 4, anomalyco/opencode#5894): this hook
|
|
19
|
+
// does not fire for tool calls issued by subagents spawned via the `task`
|
|
20
|
+
// tool — subagent-issued edits silently bypass recall injection. Documented
|
|
21
|
+
// scope limitation, not addressed by this phase.
|
|
22
|
+
//
|
|
23
|
+
// SERVER_ENTRY carries the install-rendered @@INFRA_ROOT@@ token (mirrors
|
|
24
|
+
// memory-wakeup.ts / memory-capture.ts's existing convention — the sync
|
|
25
|
+
// script, not this plugin, resolves it to the real repo root at install
|
|
26
|
+
// time; never accept a runtime-overridable server path here — T-04-03).
|
|
27
|
+
//
|
|
28
|
+
// Fail-open everywhere (D-03): a missing server binary, missing
|
|
29
|
+
// .agentfs/.planning, or any unexpected error must never block an edit.
|
|
30
|
+
|
|
31
|
+
const SERVER_ENTRY = "@@INFRA_ROOT@@/mcp-memory-server/dist/index.js"
|
|
32
|
+
const MIN_STEM_LENGTH = 4
|
|
33
|
+
const MAX_MEMORY_HITS = 8
|
|
34
|
+
const MAX_CONTEXT_LINES = 40
|
|
35
|
+
|
|
36
|
+
// Confines a candidate wiki source file to inside `.planning/wiki/sources/`
|
|
37
|
+
// using relative()-based containment (Phase 2 SEC-0001 pattern) — `resolve()
|
|
38
|
+
// === join()` misses `../` traversal, so this checks the relative path
|
|
39
|
+
// instead. The untrusted file path being edited is never concatenated into
|
|
40
|
+
// this read path; it is only used earlier to derive the `stem` grep token.
|
|
41
|
+
function isContained(baseDir: string, candidate: string): boolean {
|
|
42
|
+
const rel = path.relative(baseDir, candidate)
|
|
43
|
+
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const MemoryRecallPlugin: Plugin = async ({ $, directory }) => {
|
|
47
|
+
const surfaced = new Set<string>()
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
"tool.execute.before": async (input, output) => {
|
|
51
|
+
try {
|
|
52
|
+
if (input.tool !== "edit" && input.tool !== "write") return
|
|
53
|
+
|
|
54
|
+
const filePath: string | undefined = output.args?.filePath ?? output.args?.path
|
|
55
|
+
if (!filePath) return
|
|
56
|
+
|
|
57
|
+
const repo = directory
|
|
58
|
+
const agentfsDb = path.join(repo, ".agentfs", "project.db")
|
|
59
|
+
const wikiSourcesDir = path.join(repo, ".planning", "wiki", "sources")
|
|
60
|
+
const hasAgentfs = fs.existsSync(agentfsDb)
|
|
61
|
+
const hasWiki = fs.existsSync(wikiSourcesDir)
|
|
62
|
+
if (!hasAgentfs && !hasWiki) return
|
|
63
|
+
|
|
64
|
+
const base = path.basename(filePath)
|
|
65
|
+
const stem = base.slice(0, base.length - path.extname(base).length)
|
|
66
|
+
// Low-noise rule (D-10): skip tiny/generic stems that would match
|
|
67
|
+
// too broadly and turn this into noise on routine edits.
|
|
68
|
+
if (stem.length < MIN_STEM_LENGTH) return
|
|
69
|
+
|
|
70
|
+
// Once-per-file-per-session guard (T-04-09): after surfacing for
|
|
71
|
+
// this file once in this session, let subsequent edits/retries of
|
|
72
|
+
// the same file proceed unmodified — never re-block in a loop.
|
|
73
|
+
const dedupeKey = `${input.sessionID}:${filePath}`
|
|
74
|
+
if (surfaced.has(dedupeKey)) return
|
|
75
|
+
|
|
76
|
+
const sections: string[] = []
|
|
77
|
+
|
|
78
|
+
// 1. AgentFS project memory: filter the compact wakeup index by stem.
|
|
79
|
+
if (hasAgentfs && fs.existsSync(SERVER_ENTRY)) {
|
|
80
|
+
const res = await $`node ${SERVER_ENTRY} wakeup`.quiet().nothrow()
|
|
81
|
+
const idx = String(res.stdout ?? "").trim()
|
|
82
|
+
if (idx) {
|
|
83
|
+
const hitLines = idx
|
|
84
|
+
.split("\n")
|
|
85
|
+
.filter((line) => line.toLowerCase().includes(stem.toLowerCase()))
|
|
86
|
+
.slice(0, MAX_MEMORY_HITS)
|
|
87
|
+
if (hitLines.length > 0) {
|
|
88
|
+
sections.push(`## Relevant project memory for ${base}`, "", hitLines.join("\n"))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 2. Wiki source pages (top level only) whose content mentions the
|
|
94
|
+
// stem. Reads are confined to wikiSourcesDir via isContained() —
|
|
95
|
+
// filePath (untrusted) only supplied the stem grep token above, it
|
|
96
|
+
// is never used to build a read path here.
|
|
97
|
+
if (hasWiki) {
|
|
98
|
+
const wikiHits: string[] = []
|
|
99
|
+
const entries = fs.readdirSync(wikiSourcesDir, { withFileTypes: true })
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue
|
|
102
|
+
const pagePath = path.join(wikiSourcesDir, entry.name)
|
|
103
|
+
if (!isContained(wikiSourcesDir, pagePath)) continue
|
|
104
|
+
const content = fs.readFileSync(pagePath, "utf8")
|
|
105
|
+
if (!content.toLowerCase().includes(stem.toLowerCase())) continue
|
|
106
|
+
const teaserMatch = content.match(/^- \*\*.*$/m)
|
|
107
|
+
const teaser = teaserMatch ? teaserMatch[0].slice(0, 160) : ""
|
|
108
|
+
wikiHits.push(`- [${entry.name}] ${teaser}`)
|
|
109
|
+
}
|
|
110
|
+
if (wikiHits.length > 0) {
|
|
111
|
+
sections.push(`## Relevant wiki pages for ${base}`, "", wikiHits.join("\n"))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Inject only when there is something specific (high-signal /
|
|
116
|
+
// low-noise) — routine edits with no match proceed silently.
|
|
117
|
+
if (sections.length === 0) return
|
|
118
|
+
|
|
119
|
+
surfaced.add(dedupeKey)
|
|
120
|
+
const context = sections.join("\n\n").split("\n").slice(0, MAX_CONTEXT_LINES).join("\n")
|
|
121
|
+
throw new Error(`Memory recall (auto-injected for this file edit):\n\n${context}`)
|
|
122
|
+
} catch (err) {
|
|
123
|
+
// Re-throw our own intentional surface-context error (it carries the
|
|
124
|
+
// "Memory recall" prefix); swallow everything else to fail open —
|
|
125
|
+
// a lookup failure must never block an edit (D-03).
|
|
126
|
+
if (err instanceof Error && err.message.startsWith("Memory recall (auto-injected")) {
|
|
127
|
+
throw err
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin"
|
|
2
|
+
import fs from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
// OpenCode-native reimplementation of the Claude SessionStart `memory-wakeup`
|
|
6
|
+
// hook (OCP-05, D-04). This plugin no longer shells out to
|
|
7
|
+
// ~/.claude/hooks/memory-wakeup.sh — it surfaces the same four session-start
|
|
8
|
+
// sections natively, calling the shared cairn-memory server directly and
|
|
9
|
+
// reading the same .planning/ files, so OpenCode stands on its own with no
|
|
10
|
+
// Claude-rendered assets on disk.
|
|
11
|
+
//
|
|
12
|
+
// SERVER_ENTRY carries the install-rendered @@INFRA_ROOT@@ token (mirrors the
|
|
13
|
+
// Claude hook's substitution — the sync script, not this plugin, resolves it
|
|
14
|
+
// to the real repo root at install time; never accept a runtime-overridable
|
|
15
|
+
// server path here).
|
|
16
|
+
//
|
|
17
|
+
// Fail-open everywhere (D-03): a missing server binary, missing
|
|
18
|
+
// .agentfs/.planning, or a failed subcommand must never wedge an OpenCode
|
|
19
|
+
// session.
|
|
20
|
+
|
|
21
|
+
const SERVER_ENTRY = "@@INFRA_ROOT@@/mcp-memory-server/dist/index.js"
|
|
22
|
+
|
|
23
|
+
export const MemoryWakeupPlugin: Plugin = async ({ $, directory }) => {
|
|
24
|
+
return {
|
|
25
|
+
// No per-session dedupe: this hook fires more than once per session
|
|
26
|
+
// (including OpenCode's internal title-generation call, which happens
|
|
27
|
+
// before the first real agent turn and shares the same sessionID). A
|
|
28
|
+
// "surface once per session" Set keyed on sessionID would mark the
|
|
29
|
+
// session surfaced on that throwaway title-gen call and silently skip
|
|
30
|
+
// every real turn afterward — the OCP-05 acceptance gate never sees the
|
|
31
|
+
// injected context. `output.system` is a fresh array per call, so
|
|
32
|
+
// re-pushing on every invocation is both correct and required.
|
|
33
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
34
|
+
try {
|
|
35
|
+
const repo = directory
|
|
36
|
+
const agentfsDb = path.join(repo, ".agentfs", "project.db")
|
|
37
|
+
const wikiIndex = path.join(repo, ".planning", "wiki", "index.md")
|
|
38
|
+
const hasAgentfs = fs.existsSync(agentfsDb)
|
|
39
|
+
const hasWiki = fs.existsSync(wikiIndex)
|
|
40
|
+
if (!hasAgentfs && !hasWiki) return
|
|
41
|
+
|
|
42
|
+
const sections: string[] = []
|
|
43
|
+
|
|
44
|
+
if (hasAgentfs) {
|
|
45
|
+
const res = await $`node ${SERVER_ENTRY} wakeup`.quiet().nothrow()
|
|
46
|
+
const memory = String(res.stdout ?? "").trim()
|
|
47
|
+
sections.push("## Project memory (AgentFS)")
|
|
48
|
+
if (memory) sections.push(memory)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (hasWiki) {
|
|
52
|
+
const wiki = fs.readFileSync(wikiIndex, "utf8").trim()
|
|
53
|
+
sections.push("## Wiki index")
|
|
54
|
+
if (wiki) sections.push(wiki)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Surface open HARD wiki contradictions so the agent (and user) see
|
|
58
|
+
// them at session start without anyone having to remember to scan the
|
|
59
|
+
// register. Hard entries cannot both be correct and must be resolved
|
|
60
|
+
// before dependent work.
|
|
61
|
+
const contradictionsPath = path.join(repo, ".planning", "wiki", "CONTRADICTIONS.md")
|
|
62
|
+
if (fs.existsSync(contradictionsPath)) {
|
|
63
|
+
const raw = fs.readFileSync(contradictionsPath, "utf8")
|
|
64
|
+
const start = raw.indexOf("<!-- wiki:contradictions:open:start -->")
|
|
65
|
+
const end = raw.indexOf("<!-- wiki:contradictions:open:end -->")
|
|
66
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
67
|
+
const region = raw.slice(start, end)
|
|
68
|
+
const hardLines = region
|
|
69
|
+
.split("\n")
|
|
70
|
+
.filter((line) => /severity:\s*hard/i.test(line))
|
|
71
|
+
if (hardLines.length > 0) {
|
|
72
|
+
sections.push("## Open HARD contradictions — resolve before dependent work")
|
|
73
|
+
sections.push(hardLines.join("\n"))
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Surface staged memory candidates captured by the session-end
|
|
79
|
+
// capture plugin. These are extracted automatically from the last
|
|
80
|
+
// session but NOT yet written to AgentFS — /memory-review is the
|
|
81
|
+
// accept gate.
|
|
82
|
+
const stagingDir = path.join(repo, ".planning", "memory-staging")
|
|
83
|
+
if (fs.existsSync(stagingDir)) {
|
|
84
|
+
const staged = fs.readdirSync(stagingDir).filter((f) => f.endsWith(".json"))
|
|
85
|
+
if (staged.length > 0) {
|
|
86
|
+
sections.push(`## Staged memory candidates (${staged.length} session(s)) — UNREVIEWED`)
|
|
87
|
+
sections.push("Run /memory-review to accept (→ AgentFS) or discard these before doing other work.")
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (sections.length === 0) return
|
|
92
|
+
|
|
93
|
+
output.system.push(
|
|
94
|
+
"Session-start context (auto-surfaced by the memory-wakeup plugin — use it; do not ask the user to recall anything it contains):",
|
|
95
|
+
)
|
|
96
|
+
output.system.push(sections.join("\n\n"))
|
|
97
|
+
} catch {
|
|
98
|
+
// Fail open — never block a session because context surfacing failed.
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
project: REPLACE_WITH_PROJECT_NAME
|
|
3
|
+
default_branch: REPLACE_WITH_DEFAULT_BRANCH
|
|
4
|
+
last_security_audit:
|
|
5
|
+
at: null
|
|
6
|
+
branch: null
|
|
7
|
+
report: null
|
|
8
|
+
findings: []
|
|
9
|
+
|
|
10
|
+
# Finding entry shape:
|
|
11
|
+
# - id: SEC-0001
|
|
12
|
+
# title: Unsafe shell command composition in archive import helper
|
|
13
|
+
# kind: command_execution
|
|
14
|
+
# severity: high
|
|
15
|
+
# status: open
|
|
16
|
+
# confidence: confirmed
|
|
17
|
+
# opened_by: security-audit
|
|
18
|
+
# opened_at: 2026-04-17T18:00:00Z
|
|
19
|
+
# branch: feature/archive-import
|
|
20
|
+
# threat_model: authenticated HTTP request
|
|
21
|
+
# exploit_surface: POST /api/import
|
|
22
|
+
# selected_file: src/import.rs
|
|
23
|
+
# investigation_target: shell command construction for imported filenames
|
|
24
|
+
# evidence:
|
|
25
|
+
# files:
|
|
26
|
+
# - src/import.rs
|
|
27
|
+
# commands:
|
|
28
|
+
# - cargo test --test import
|
|
29
|
+
# inputs:
|
|
30
|
+
# - multipart upload filename payload
|
|
31
|
+
# reports:
|
|
32
|
+
# - .planning/security/VALIDATED/SEC-0001-unsafe-shell-command-composition.md
|
|
33
|
+
# duplication:
|
|
34
|
+
# duplicate_of: null
|
|
35
|
+
# related_ids: []
|
|
36
|
+
# validation:
|
|
37
|
+
# verdict: accepted
|
|
38
|
+
# report_path: .planning/security/VALIDATED/SEC-0001-unsafe-shell-command-composition.md
|
|
39
|
+
# validated_at: 2026-04-17T18:00:00Z
|
|
40
|
+
# rationale: Reachable from the standard import flow without repo-external assumptions.
|
|
41
|
+
# remediation:
|
|
42
|
+
# status: open
|
|
43
|
+
# fixed_in: null
|
|
44
|
+
# verified_at: null
|
|
45
|
+
# tracking:
|
|
46
|
+
# provider_issue:
|
|
47
|
+
# project: group/project
|
|
48
|
+
# iid: "123"
|
|
49
|
+
# web_url: https://git.example.com/group/project/-/issues/123
|
|
50
|
+
# confidential: true
|
|
51
|
+
# created_at: 2026-04-17T18:00:00Z
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Confidential Security Issue Draft
|
|
2
|
+
|
|
3
|
+
- Project: REPLACE_WITH_PROJECT_NAME
|
|
4
|
+
- Provider project: REPLACE_WITH_PROJECT
|
|
5
|
+
- Finding ID: REPLACE_WITH_FINDING_ID
|
|
6
|
+
- Severity: REPLACE_WITH_SEVERITY
|
|
7
|
+
- Kind: REPLACE_WITH_KIND
|
|
8
|
+
- Selected file: REPLACE_WITH_SELECTED_FILE
|
|
9
|
+
- Investigation target: REPLACE_WITH_INVESTIGATION_TARGET
|
|
10
|
+
- Threat model: REPLACE_WITH_THREAT_MODEL
|
|
11
|
+
- Branch: REPLACE_WITH_BRANCH
|
|
12
|
+
- Generated at: REPLACE_WITH_TIMESTAMP
|
|
13
|
+
- Source command: /security-audit
|
|
14
|
+
- Source report: REPLACE_WITH_REPORT_PATH
|
|
15
|
+
|
|
16
|
+
## Summary
|
|
17
|
+
|
|
18
|
+
REPLACE_WITH_SUMMARY
|
|
19
|
+
|
|
20
|
+
## Reachability And Impact
|
|
21
|
+
|
|
22
|
+
REPLACE_WITH_REACHABILITY_AND_IMPACT
|
|
23
|
+
|
|
24
|
+
## Evidence
|
|
25
|
+
|
|
26
|
+
REPLACE_WITH_EVIDENCE
|
|
27
|
+
|
|
28
|
+
## Minimal Reproduction Or Exploit Sketch
|
|
29
|
+
|
|
30
|
+
REPLACE_WITH_REPRODUCTION
|
|
31
|
+
|
|
32
|
+
## Recommended Remediation
|
|
33
|
+
|
|
34
|
+
REPLACE_WITH_REMEDIATION
|
|
35
|
+
|
|
36
|
+
## Local Artifact References
|
|
37
|
+
|
|
38
|
+
- Validated report: REPLACE_WITH_VALIDATED_REPORT
|
|
39
|
+
- Audit report: REPLACE_WITH_AUDIT_REPORT
|
|
40
|
+
|
|
41
|
+
## Confidentiality Note
|
|
42
|
+
|
|
43
|
+
This issue was generated from a governed local security assessment and should remain confidential until remediation and disclosure decisions are made.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Security Assessment Policy
|
|
2
|
+
|
|
3
|
+
This repository uses the governed security-assessment workflow to hunt for realistic security issues without turning normal development into uncontrolled offensive automation.
|
|
4
|
+
|
|
5
|
+
## Branch Rules
|
|
6
|
+
|
|
7
|
+
- Default branch: `REPLACE_WITH_DEFAULT_BRANCH`
|
|
8
|
+
- Do not push feature work directly to `REPLACE_WITH_DEFAULT_BRANCH`.
|
|
9
|
+
- Use feature branches for remediation and keep candidate reports under `.planning/security/CANDIDATES/` until a finding is validated.
|
|
10
|
+
|
|
11
|
+
## Safety Rules
|
|
12
|
+
|
|
13
|
+
- Run `/security-audit` only against this repository, repo-owned local processes, local files, and localhost services launched from this repo.
|
|
14
|
+
- Do not target external hosts, staging, production, or third-party systems.
|
|
15
|
+
- Keep proofs local, minimal, and non-destructive.
|
|
16
|
+
- Do not attempt persistence, credential harvesting, lateral movement, or data exfiltration.
|
|
17
|
+
|
|
18
|
+
## Finding Lifecycle
|
|
19
|
+
|
|
20
|
+
- Open or refresh findings with `/security-audit`.
|
|
21
|
+
- Keep rejected or duplicate candidates in run history only; do not copy them into the canonical ledger.
|
|
22
|
+
- Only accepted findings belong in `.planning/security/FINDINGS.yaml` and `.planning/security/VALIDATED/`.
|
|
23
|
+
- Close or downgrade findings only after remediation or explicit acceptance on the default branch.
|
|
24
|
+
|
|
25
|
+
## Review Expectations
|
|
26
|
+
|
|
27
|
+
- Critical and high-severity accepted findings should block merge until triaged by a human.
|
|
28
|
+
- Treat the repo-local findings ledger as the shared state for severity, status, and remediation progress.
|
|
29
|
+
- If a run cannot prove reachability convincingly, prefer rejection over speculative acceptance.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Security Audit Report
|
|
2
|
+
|
|
3
|
+
- Project: REPLACE_WITH_PROJECT_NAME
|
|
4
|
+
- Audit scope: diff | full
|
|
5
|
+
- Base ref: REPLACE_WITH_BASE_REF
|
|
6
|
+
- Head ref: REPLACE_WITH_HEAD_REF
|
|
7
|
+
- Threat model: REPLACE_WITH_THREAT_MODEL
|
|
8
|
+
- Focus path: REPLACE_WITH_FOCUS_PATH
|
|
9
|
+
- Generated at: REPLACE_WITH_TIMESTAMP
|
|
10
|
+
- Generated by: security-audit
|
|
11
|
+
|
|
12
|
+
## Summary
|
|
13
|
+
|
|
14
|
+
- Accepted findings: 0
|
|
15
|
+
- Rejected candidates: 0
|
|
16
|
+
- Selected file: REPLACE_WITH_SELECTED_FILE
|
|
17
|
+
- Investigation target: REPLACE_WITH_INVESTIGATION_TARGET
|
|
18
|
+
|
|
19
|
+
## Accepted Findings
|
|
20
|
+
|
|
21
|
+
### SEC-0001
|
|
22
|
+
|
|
23
|
+
- Title: REPLACE_WITH_TITLE
|
|
24
|
+
- Kind: command_execution
|
|
25
|
+
- Severity: high
|
|
26
|
+
- Report: REPLACE_WITH_REPORT_PATH
|
|
27
|
+
|
|
28
|
+
#### Why this matters
|
|
29
|
+
|
|
30
|
+
REPLACE_WITH_IMPACT
|
|
31
|
+
|
|
32
|
+
#### Recommended next step
|
|
33
|
+
|
|
34
|
+
REPLACE_WITH_NEXT_STEP
|
|
35
|
+
|
|
36
|
+
## Rejected Candidates
|
|
37
|
+
|
|
38
|
+
- None
|
|
39
|
+
|
|
40
|
+
## Safety Notes
|
|
41
|
+
|
|
42
|
+
- This run was limited to repo-local analysis and locally launched services or local files.
|
|
43
|
+
- Proofs should stay minimal and non-destructive.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Wiki Contradiction Register
|
|
2
|
+
|
|
3
|
+
> Persistent register of contradictions flagged across `.planning/wiki/`.
|
|
4
|
+
> Updated in place by `/wiki-lint`. Unlike per-run REPORTS, this file is the
|
|
5
|
+
> durable record — lint opens and resolves entries here rather than emitting
|
|
6
|
+
> write-and-forget reports. Raw repo docs, tests, interfaces, config, and code
|
|
7
|
+
> remain canonical; this register only tracks where the *derived* wiki disagrees.
|
|
8
|
+
|
|
9
|
+
## Severity Taxonomy
|
|
10
|
+
|
|
11
|
+
- **soft** — non-blocking, contextual difference. Pages can coexist; flagged for awareness. No action required unless it accumulates.
|
|
12
|
+
- **scope-mismatch** — the two claims apply to different scopes/contexts and do not truly conflict. Recorded so a future reader is not confused; coexists.
|
|
13
|
+
- **hard** — genuinely conflicting claims that cannot both be correct. Must be resolved before downstream work relies on either page. Surface at session start.
|
|
14
|
+
|
|
15
|
+
## How To Use
|
|
16
|
+
|
|
17
|
+
- `/wiki-lint` writes/updates entries here on every run. Do not delete open entries by hand — resolve the underlying page disagreement, then let lint (or a `/wiki-ingest --refresh`) flip the status to `resolved`.
|
|
18
|
+
- At session start, scan the **Open** section for `severity: hard`. Surface any hard/open items before doing work that depends on the affected pages.
|
|
19
|
+
- An entry is `resolved` only after the conflicting wiki page(s) have actually been reconciled to the canonical source — not merely acknowledged.
|
|
20
|
+
|
|
21
|
+
## Open
|
|
22
|
+
|
|
23
|
+
<!-- wiki:contradictions:open:start -->
|
|
24
|
+
- None.
|
|
25
|
+
<!-- wiki:contradictions:open:end -->
|
|
26
|
+
|
|
27
|
+
## Resolved
|
|
28
|
+
|
|
29
|
+
<!-- wiki:contradictions:resolved:start -->
|
|
30
|
+
- None.
|
|
31
|
+
<!-- wiki:contradictions:resolved:end -->
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# REPLACE_WITH_PROJECT_NAME Knowledge Wiki
|
|
2
|
+
|
|
3
|
+
> Derived, sparse, citation-heavy notes for faster repository understanding.
|
|
4
|
+
> Canonical truth remains in raw repository docs, tests, interfaces, config, and code.
|
|
5
|
+
|
|
6
|
+
## How To Use This Wiki
|
|
7
|
+
|
|
8
|
+
- Start here to find source summaries, optional topic and entity pages, reusable query answers, and lint reports.
|
|
9
|
+
- Follow the cited canonical sources before treating any derived claim as settled.
|
|
10
|
+
- If the wiki conflicts with raw sources, trust the raw sources and note the contradiction.
|
|
11
|
+
|
|
12
|
+
## Sources
|
|
13
|
+
|
|
14
|
+
<!-- wiki:sources:start -->
|
|
15
|
+
- None yet.
|
|
16
|
+
<!-- wiki:sources:end -->
|
|
17
|
+
|
|
18
|
+
## Topics
|
|
19
|
+
|
|
20
|
+
<!-- wiki:topics:start -->
|
|
21
|
+
- None yet.
|
|
22
|
+
<!-- wiki:topics:end -->
|
|
23
|
+
|
|
24
|
+
## Entities
|
|
25
|
+
|
|
26
|
+
<!-- wiki:entities:start -->
|
|
27
|
+
- None yet.
|
|
28
|
+
<!-- wiki:entities:end -->
|
|
29
|
+
|
|
30
|
+
## Queries
|
|
31
|
+
|
|
32
|
+
<!-- wiki:queries:start -->
|
|
33
|
+
- None yet.
|
|
34
|
+
<!-- wiki:queries:end -->
|
|
35
|
+
|
|
36
|
+
## Reports
|
|
37
|
+
|
|
38
|
+
<!-- wiki:reports:start -->
|
|
39
|
+
- None yet.
|
|
40
|
+
<!-- wiki:reports:end -->
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Wiki Lint Report
|
|
2
|
+
|
|
3
|
+
- Project: REPLACE_WITH_PROJECT_NAME
|
|
4
|
+
- Generated at: REPLACE_WITH_TIMESTAMP
|
|
5
|
+
- Scope: REPLACE_WITH_SCOPE
|
|
6
|
+
- Advisory status: Raw repository docs, tests, interfaces, config, and code remain canonical.
|
|
7
|
+
|
|
8
|
+
## Summary
|
|
9
|
+
|
|
10
|
+
- Missing citations: 0
|
|
11
|
+
- Orphan pages: 0
|
|
12
|
+
- Stale claims: 0
|
|
13
|
+
- Contradicted claims: 0 (hard: 0 / scope-mismatch: 0 / soft: 0)
|
|
14
|
+
- Missing obvious pages: 0
|
|
15
|
+
- Broken cross-references: 0
|
|
16
|
+
|
|
17
|
+
> Contradiction entries are also written to `.planning/wiki/CONTRADICTIONS.md`,
|
|
18
|
+
> the persistent register. This report is a snapshot; the register is durable.
|
|
19
|
+
|
|
20
|
+
## Contradictions (severity-tagged)
|
|
21
|
+
|
|
22
|
+
- None.
|
|
23
|
+
|
|
24
|
+
## High Signal Findings
|
|
25
|
+
|
|
26
|
+
- None.
|
|
27
|
+
|
|
28
|
+
## Medium Signal Findings
|
|
29
|
+
|
|
30
|
+
- None.
|
|
31
|
+
|
|
32
|
+
## Low Signal Findings
|
|
33
|
+
|
|
34
|
+
- None.
|
|
35
|
+
|
|
36
|
+
## Suggested Next Steps
|
|
37
|
+
|
|
38
|
+
- Keep the wiki sparse, citation-heavy, and aligned to canonical raw sources.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Wiki Log
|
|
2
|
+
|
|
3
|
+
This log records wiki ingests, reusable query writebacks, and advisory lint runs.
|
|
4
|
+
|
|
5
|
+
Raw repository docs, tests, interfaces, config, and code remain canonical.
|
|
6
|
+
|
|
7
|
+
## Entries
|
|
8
|
+
|
|
9
|
+
<!-- wiki:entries:start -->
|
|
10
|
+
- Scaffold initialized. Append timestamped `wiki-ingest`, `wiki-query --writeback`, and `wiki-lint` entries below.
|
|
11
|
+
<!-- wiki:entries:end -->
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Project Knowledge Wiki Policy
|
|
2
|
+
|
|
3
|
+
This repository uses `.planning/wiki/` as a derived, sparse, citation-heavy compiled-knowledge layer.
|
|
4
|
+
|
|
5
|
+
## Canonical Sources
|
|
6
|
+
|
|
7
|
+
- Raw repository docs, ADRs, tests, interfaces, config, and code remain the canonical sources of truth.
|
|
8
|
+
- The wiki must never overrule the default-branch implementation or canonical repository documents.
|
|
9
|
+
- Use wiki pages to summarize, cross-link, and preserve reusable answers, not to duplicate every file.
|
|
10
|
+
|
|
11
|
+
## Branch Rules
|
|
12
|
+
|
|
13
|
+
- Project: `REPLACE_WITH_PROJECT_NAME`
|
|
14
|
+
- Default branch: `REPLACE_WITH_DEFAULT_BRANCH`
|
|
15
|
+
- Update wiki pages on the same branch as the source changes they depend on.
|
|
16
|
+
- If the wiki and raw sources disagree, record the contradiction explicitly instead of silently normalizing it.
|
|
17
|
+
|
|
18
|
+
## Provenance Rules
|
|
19
|
+
|
|
20
|
+
- Every non-trivial claim in `sources/`, `topics/`, `entities/`, and `queries/` should cite canonical source paths.
|
|
21
|
+
- Prefer direct repo-relative paths and specific sections or line ranges when practical.
|
|
22
|
+
- Record `Last reviewed` on every derived page.
|
|
23
|
+
- If freshness is uncertain, say so directly.
|
|
24
|
+
|
|
25
|
+
## Scope Rules
|
|
26
|
+
|
|
27
|
+
- Do not auto-generate a wiki page per code file.
|
|
28
|
+
- Prefer source summaries for repository docs, design docs, ADRs, standards, and a small number of stable interfaces or subsystems.
|
|
29
|
+
- Topic and entity pages are optional and rare. Create them only when the same synthesis is needed across three or more sources or recurring queries. An empty `topics/`, `entities/`, or `queries/` directory is a healthy, expected state — a sources-only wiki is the normal stable shape for engineering repos, not a gap to fill.
|
|
30
|
+
|
|
31
|
+
## Contradiction Severity
|
|
32
|
+
|
|
33
|
+
When the wiki disagrees with a canonical source or with itself, classify and record it in `.planning/wiki/CONTRADICTIONS.md` (the persistent register) using one of:
|
|
34
|
+
|
|
35
|
+
- **soft** — non-blocking, contextual difference; coexists, no action required.
|
|
36
|
+
- **scope-mismatch** — the claims apply to different scopes and do not truly conflict; recorded to prevent confusion.
|
|
37
|
+
- **hard** — genuinely conflicting claims that cannot both be correct; must be resolved before downstream work relies on either page. Surfaced at session start.
|
|
38
|
+
|
|
39
|
+
`/wiki-lint` opens and resolves entries in the register in place. An entry is `resolved` only after the conflicting wiki page has actually been reconciled to the canonical source, not merely acknowledged. Do not silently normalize a contradiction away — if raw sources disagree, record it explicitly.
|
|
40
|
+
|
|
41
|
+
## Maintenance
|
|
42
|
+
|
|
43
|
+
- Use `/wiki-ingest <path>` to create a source summary, or `/wiki-ingest <path> --refresh` to re-sync an existing summary to the current canonical source (re-read, update only changed stable facts, bump `Last reviewed`, log a `wiki-refresh` entry). Run a refresh whenever the MR/PR or code state behind a page changes.
|
|
44
|
+
- Use `/wiki-query --writeback <question>` only when the answer is likely reusable.
|
|
45
|
+
- Use `/wiki-lint` for advisory checks on citations, freshness, contradictions, discoverability, and link hygiene. Lint also updates the persistent `CONTRADICTIONS.md` register.
|
|
46
|
+
- At session start, scan `CONTRADICTIONS.md` for any `severity: hard` open entries and surface them before doing dependent work.
|
|
47
|
+
- Lint reports under `REPORTS/` are advisory snapshots; the register is the durable record. Raw docs and code remain the source of truth.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Query: REPLACE_WITH_QUERY_TITLE
|
|
2
|
+
|
|
3
|
+
- Asked at: `REPLACE_WITH_TIMESTAMP`
|
|
4
|
+
- Question: REPLACE_WITH_QUESTION
|
|
5
|
+
- Answer status: `derived`
|
|
6
|
+
- Last reviewed: `REPLACE_WITH_TIMESTAMP`
|
|
7
|
+
|
|
8
|
+
## Answer
|
|
9
|
+
|
|
10
|
+
REPLACE_WITH_ANSWER
|
|
11
|
+
|
|
12
|
+
## Wiki Pages Consulted
|
|
13
|
+
|
|
14
|
+
- REPLACE_WITH_WIKI_PAGE
|
|
15
|
+
|
|
16
|
+
## Canonical Sources
|
|
17
|
+
|
|
18
|
+
- REPLACE_WITH_CANONICAL_SOURCE
|
|
19
|
+
|
|
20
|
+
## Contradictions And Freshness Notes
|
|
21
|
+
|
|
22
|
+
- None noted.
|
|
23
|
+
|
|
24
|
+
## Suggested Follow-up
|
|
25
|
+
|
|
26
|
+
- None.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Source: REPLACE_WITH_SOURCE_LABEL
|
|
2
|
+
|
|
3
|
+
- Canonical source: `REPLACE_WITH_SOURCE_PATH`
|
|
4
|
+
- Source kind: `REPLACE_WITH_SOURCE_KIND`
|
|
5
|
+
- Derived status: `active`
|
|
6
|
+
- Last reviewed: `REPLACE_WITH_LAST_REVIEWED`
|
|
7
|
+
|
|
8
|
+
## Why This Source Matters
|
|
9
|
+
|
|
10
|
+
REPLACE_WITH_WHY_THIS_MATTERS
|
|
11
|
+
|
|
12
|
+
## Stable Facts
|
|
13
|
+
|
|
14
|
+
- REPLACE_WITH_FACT_AND_CITATION
|
|
15
|
+
|
|
16
|
+
## Contradictions And Freshness Notes
|
|
17
|
+
|
|
18
|
+
- None noted.
|
|
19
|
+
|
|
20
|
+
When a contradiction exists, tag it with a severity so `/wiki-lint` can mirror
|
|
21
|
+
it into `CONTRADICTIONS.md`. Use one of:
|
|
22
|
+
- `Contradiction severity: soft` — contextual, coexists.
|
|
23
|
+
- `Contradiction severity: scope-mismatch` — different scopes, not a true conflict.
|
|
24
|
+
- `Contradiction severity: hard` — genuinely conflicting, must resolve.
|
|
25
|
+
Add a `Status: open` (or `resolved`) line so lint can track it deterministically.
|
|
26
|
+
|
|
27
|
+
## Related Wiki Pages
|
|
28
|
+
|
|
29
|
+
- None yet.
|
|
30
|
+
|
|
31
|
+
## Canonical References
|
|
32
|
+
|
|
33
|
+
- `REPLACE_WITH_SOURCE_PATH`
|