merge: integrate origin/dev into modular-enforcement branch
Resolves all merge conflicts, preserving our split module structure while integrating all dev changes: - Custom agent summaries support (parseRegisteredAgentSummaries) - Background notification queue (enqueueNotificationForParent) - Atlas shared git-worktree module (collectGitDiffStats, formatFileChanges) - Ralph-loop withTimeout + DEFAULT_API_TIMEOUT=5000 - Session recovery assistant_prefill_unsupported error type - Atlas agentOverrides forwarding - Config handler plan model demotion (buildPlanDemoteConfig) - Delegate-task agentOverrides, promptSyncWithModelSuggestionRetry, variant - LSP init timeout + stale init detection - isPlanFamily function + task-continuation-enforcer hook - Handoff command
This commit is contained in:
@@ -1,133 +0,0 @@
|
||||
import { execSync } from "node:child_process"
|
||||
|
||||
interface GitFileStat {
|
||||
path: string
|
||||
added: number
|
||||
removed: number
|
||||
status: "modified" | "added" | "deleted"
|
||||
}
|
||||
|
||||
export function getGitDiffStats(directory: string): GitFileStat[] {
|
||||
try {
|
||||
const statusOutput = execSync("git status --porcelain", {
|
||||
cwd: directory,
|
||||
encoding: "utf-8",
|
||||
timeout: 5000,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
}).trim()
|
||||
|
||||
if (!statusOutput) return []
|
||||
|
||||
const statusMap = new Map<string, "modified" | "added" | "deleted">()
|
||||
const untrackedFiles: string[] = []
|
||||
for (const line of statusOutput.split("\n")) {
|
||||
if (!line) continue
|
||||
const status = line.substring(0, 2).trim()
|
||||
const filePath = line.substring(3)
|
||||
if (status === "??") {
|
||||
statusMap.set(filePath, "added")
|
||||
untrackedFiles.push(filePath)
|
||||
} else if (status === "A") {
|
||||
statusMap.set(filePath, "added")
|
||||
} else if (status === "D") {
|
||||
statusMap.set(filePath, "deleted")
|
||||
} else {
|
||||
statusMap.set(filePath, "modified")
|
||||
}
|
||||
}
|
||||
|
||||
const output = execSync("git diff --numstat HEAD", {
|
||||
cwd: directory,
|
||||
encoding: "utf-8",
|
||||
timeout: 5000,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
}).trim()
|
||||
|
||||
const stats: GitFileStat[] = []
|
||||
const trackedPaths = new Set<string>()
|
||||
|
||||
if (output) {
|
||||
for (const line of output.split("\n")) {
|
||||
const parts = line.split("\t")
|
||||
if (parts.length < 3) continue
|
||||
|
||||
const [addedStr, removedStr, path] = parts
|
||||
const added = addedStr === "-" ? 0 : parseInt(addedStr, 10)
|
||||
const removed = removedStr === "-" ? 0 : parseInt(removedStr, 10)
|
||||
trackedPaths.add(path)
|
||||
|
||||
stats.push({
|
||||
path,
|
||||
added,
|
||||
removed,
|
||||
status: statusMap.get(path) ?? "modified",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const filePath of untrackedFiles) {
|
||||
if (trackedPaths.has(filePath)) continue
|
||||
try {
|
||||
const content = execSync(`wc -l < "${filePath}"`, {
|
||||
cwd: directory,
|
||||
encoding: "utf-8",
|
||||
timeout: 3000,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
}).trim()
|
||||
const lineCount = parseInt(content, 10) || 0
|
||||
stats.push({ path: filePath, added: lineCount, removed: 0, status: "added" })
|
||||
} catch {
|
||||
stats.push({ path: filePath, added: 0, removed: 0, status: "added" })
|
||||
}
|
||||
}
|
||||
|
||||
return stats
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export function formatFileChanges(stats: GitFileStat[], notepadPath?: string): string {
|
||||
if (stats.length === 0) return "[FILE CHANGES SUMMARY]\nNo file changes detected.\n"
|
||||
|
||||
const modified = stats.filter((s) => s.status === "modified")
|
||||
const added = stats.filter((s) => s.status === "added")
|
||||
const deleted = stats.filter((s) => s.status === "deleted")
|
||||
|
||||
const lines: string[] = ["[FILE CHANGES SUMMARY]"]
|
||||
|
||||
if (modified.length > 0) {
|
||||
lines.push("Modified files:")
|
||||
for (const f of modified) {
|
||||
lines.push(` ${f.path} (+${f.added}, -${f.removed})`)
|
||||
}
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
if (added.length > 0) {
|
||||
lines.push("Created files:")
|
||||
for (const f of added) {
|
||||
lines.push(` ${f.path} (+${f.added})`)
|
||||
}
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
if (deleted.length > 0) {
|
||||
lines.push("Deleted files:")
|
||||
for (const f of deleted) {
|
||||
lines.push(` ${f.path} (-${f.removed})`)
|
||||
}
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
if (notepadPath) {
|
||||
const notepadStat = stats.find((s) => s.path.includes("notepad") || s.path.includes(".sisyphus"))
|
||||
if (notepadStat) {
|
||||
lines.push("[NOTEPAD UPDATED]")
|
||||
lines.push(` ${notepadStat.path} (+${notepadStat.added})`)
|
||||
lines.push("")
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n")
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import { appendSessionId, getPlanProgress, readBoulderState } from "../../features/boulder-state"
|
||||
import { log } from "../../shared/logger"
|
||||
import { isCallerOrchestrator } from "../../shared/session-utils"
|
||||
import { collectGitDiffStats, formatFileChanges } from "../../shared/git-worktree"
|
||||
import { HOOK_NAME } from "./hook-name"
|
||||
import { DIRECT_WORK_REMINDER } from "./system-reminder-templates"
|
||||
import { formatFileChanges, getGitDiffStats } from "./git-diff-stats"
|
||||
import { isSisyphusPath } from "./sisyphus-path"
|
||||
import { extractSessionIdFromOutput } from "./subagent-session-id"
|
||||
import { buildOrchestratorReminder, buildStandaloneVerificationReminder } from "./verification-reminders"
|
||||
@@ -57,7 +57,7 @@ export function createToolExecuteAfterHandler(input: {
|
||||
}
|
||||
|
||||
if (toolOutput.output && typeof toolOutput.output === "string") {
|
||||
const gitStats = getGitDiffStats(ctx.directory)
|
||||
const gitStats = collectGitDiffStats(ctx.directory)
|
||||
const fileChanges = formatFileChanges(gitStats)
|
||||
const subagentSessionId = extractSessionIdFromOutput(toolOutput.output)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AgentOverrides } from "../../config"
|
||||
import type { BackgroundManager } from "../../features/background-agent"
|
||||
|
||||
export type ModelInfo = { providerID: string; modelID: string }
|
||||
@@ -6,6 +7,7 @@ export interface AtlasHookOptions {
|
||||
directory: string
|
||||
backgroundManager?: BackgroundManager
|
||||
isContinuationStopped?: (sessionID: string) => boolean
|
||||
agentOverrides?: AgentOverrides
|
||||
}
|
||||
|
||||
export interface ToolExecuteAfterInput {
|
||||
|
||||
Reference in New Issue
Block a user