254 lines
7.3 KiB
Plaintext
254 lines
7.3 KiB
Plaintext
-- allium: 1
|
|
-- bDS Git Integration
|
|
-- Scope: extension (Bucket A — Git + Validation)
|
|
-- Distilled from: src/main/engine/GitEngine.ts
|
|
|
|
use "./post.allium" as post
|
|
use "./script.allium" as script
|
|
use "./template.allium" as template
|
|
|
|
value GitProvider {
|
|
kind: github | gitlab | gitea_forgejo
|
|
-- Detected from remote URL patterns
|
|
}
|
|
|
|
value GitSyncStatus {
|
|
-- Per-commit: local_only | remote_only | both
|
|
kind: local_only | remote_only | both
|
|
}
|
|
|
|
value GitFileStatus {
|
|
path: String
|
|
old_path: String?
|
|
kind: added | modified | deleted | renamed | untracked
|
|
}
|
|
|
|
value GitCommit {
|
|
hash: String
|
|
subject: String?
|
|
author: String?
|
|
date: String?
|
|
sync_status: GitSyncStatus
|
|
}
|
|
|
|
value GitRemoteState {
|
|
local_branch: String?
|
|
upstream_branch: String?
|
|
has_upstream: Boolean
|
|
ahead: Integer
|
|
behind: Integer
|
|
}
|
|
|
|
config {
|
|
local_timeout: Duration = 15.seconds
|
|
network_timeout: Duration = 120.seconds
|
|
file_history_limit: Integer = 50
|
|
}
|
|
|
|
surface GitSyncStatusSurface {
|
|
context status: GitSyncStatus
|
|
|
|
exposes:
|
|
status.kind
|
|
}
|
|
|
|
entity GitRepository {
|
|
is_initialized: Boolean
|
|
remote_url: String?
|
|
provider: GitProvider?
|
|
current_branch: String?
|
|
has_lfs: Boolean
|
|
}
|
|
|
|
surface GitRepositorySurface {
|
|
context repo: GitRepository
|
|
|
|
exposes:
|
|
repo.is_initialized
|
|
repo.remote_url when repo.remote_url != null
|
|
repo.provider when repo.provider != null
|
|
repo.current_branch when repo.current_branch != null
|
|
repo.has_lfs
|
|
}
|
|
|
|
surface GitControlSurface {
|
|
facing _: GitOperator
|
|
|
|
exposes:
|
|
GitFileStatus
|
|
GitCommit
|
|
GitRemoteState
|
|
|
|
provides:
|
|
InitializeRepoRequested(project)
|
|
GitStatusRequested(project)
|
|
GitDiffRequested(project)
|
|
GitFileDiffRequested(project, file_path)
|
|
GitHistoryRequested(project, branch)
|
|
GitFileHistoryRequested(project, file_path)
|
|
GitRemoteStateRequested(project)
|
|
GitFetchRequested(project)
|
|
GitPullRequested(project)
|
|
GitPushRequested(project)
|
|
GitNetworkRunCancelled(project)
|
|
GitCommitAllRequested(project, message)
|
|
GitReconcileRequested(project, old_commit, new_commit)
|
|
PruneLfsCacheRequested(project, retain_recent)
|
|
}
|
|
|
|
rule InitializeRepo {
|
|
when: InitializeRepoRequested(project)
|
|
ensures: GitRepository.created(
|
|
is_initialized: true,
|
|
remote_url: null,
|
|
provider: null,
|
|
current_branch: "master",
|
|
has_lfs: true
|
|
)
|
|
ensures: GitignoreCreated(project)
|
|
-- .gitignore manages generated artifacts, cached assets, dependency directories, etc.
|
|
ensures: LfsTrackingConfigured(project)
|
|
-- Git LFS auto-tracks image patterns (*.jpg, *.png, *.gif, etc.)
|
|
}
|
|
|
|
rule GetStatus {
|
|
when: GitStatusRequested(project)
|
|
-- Returns file-level status: added, modified, deleted, renamed, untracked
|
|
ensures: GitStatusReport(files)
|
|
}
|
|
|
|
rule GetDiff {
|
|
when: GitDiffRequested(project)
|
|
ensures: GitDiffReport(staged_diff, unstaged_diff)
|
|
}
|
|
|
|
rule GetFileDiff {
|
|
when: GitFileDiffRequested(project, file_path)
|
|
-- Returns the file at HEAD and in the working tree. A missing side is
|
|
-- represented by empty content so added and deleted files remain viewable.
|
|
ensures: GitFileDiffReport(file_path, original_content, modified_content)
|
|
}
|
|
|
|
rule GetHistory {
|
|
when: GitHistoryRequested(project, branch)
|
|
-- Returns commit history with sync status per commit
|
|
ensures: GitHistoryReport(commits)
|
|
@guidance
|
|
-- Each commit annotated with: local_only, remote_only, or both
|
|
-- This drives the "push needed" / "pull needed" indicators
|
|
}
|
|
|
|
|
|
rule GetFileHistory {
|
|
when: GitFileHistoryRequested(project, file_path)
|
|
-- Follows renames and returns the newest config.file_history_limit commits.
|
|
-- A file with no history produces an empty report rather than an error.
|
|
ensures: GitFileHistoryReport(file_path, commits)
|
|
}
|
|
|
|
rule GetRemoteState {
|
|
when: GitRemoteStateRequested(project)
|
|
-- A repository without an upstream reports zero ahead/behind counts.
|
|
ensures: GitRemoteStateReport(project, local_branch, upstream_branch, ahead, behind)
|
|
}
|
|
|
|
rule Fetch {
|
|
when: GitFetchRequested(project)
|
|
ensures: RemoteRefsUpdated(project)
|
|
|
|
@guidance
|
|
-- Network output is streamed live and the run can be cancelled.
|
|
}
|
|
|
|
rule Pull {
|
|
when: GitPullRequested(project)
|
|
ensures: LocalBranchUpdated(project)
|
|
ensures: GitReconcileRequested(project, previous_head(project), current_head(project))
|
|
-- After pull, detect changed files and reconcile DB
|
|
|
|
@guidance
|
|
-- Pull is fast-forward-only. Network output is streamed live and the
|
|
-- run can be cancelled.
|
|
}
|
|
|
|
rule Push {
|
|
when: GitPushRequested(project)
|
|
ensures: RemoteBranchUpdated(project)
|
|
|
|
@guidance
|
|
-- Network output is streamed live and the run can be cancelled.
|
|
}
|
|
|
|
rule CancelNetworkRun {
|
|
when: GitNetworkRunCancelled(project)
|
|
ensures: RunningGitProcessTerminated(project)
|
|
ensures: GitNetworkRunFinished(project, cancelled)
|
|
}
|
|
|
|
rule CommitAll {
|
|
when: GitCommitAllRequested(project, message)
|
|
requires: message != ""
|
|
ensures: AllChangesStaged(project)
|
|
ensures: CommitCreated(project, message)
|
|
}
|
|
|
|
-- Git reconciliation: sync DB from filesystem changes
|
|
|
|
rule ReconcileFromGit {
|
|
when: GitReconcileRequested(project, old_commit, new_commit)
|
|
-- Detect changed files between commits for posts, scripts, templates
|
|
let post_changes = changed_post_files(old_commit, new_commit)
|
|
let script_changes = changed_script_files(old_commit, new_commit)
|
|
let template_changes = changed_template_files(old_commit, new_commit)
|
|
|
|
for added in post_changes.added:
|
|
ensures: post/Post.created(parse_post_file(added))
|
|
for modified in post_changes.modified:
|
|
ensures: PostUpdatedFromFile(modified)
|
|
for deleted in post_changes.deleted:
|
|
ensures: PostDeletedByPath(deleted)
|
|
for renamed in post_changes.renamed:
|
|
ensures: PostFileRenamed(renamed.old, renamed.new)
|
|
|
|
-- Same pattern for scripts and templates
|
|
for added in script_changes.added:
|
|
ensures: script/Script.created(parse_script_file(added))
|
|
for added in template_changes.added:
|
|
ensures: template/Template.created(parse_template_file(added))
|
|
|
|
ensures: EntityChangedEventsEmitted(project)
|
|
}
|
|
|
|
invariant NonInteractiveGit {
|
|
-- All git operations run non-interactively:
|
|
-- GIT_TERMINAL_PROMPT=0
|
|
-- GCM_INTERACTIVE=never
|
|
-- ssh -oBatchMode=yes
|
|
-- No password prompts ever surface to the user
|
|
}
|
|
|
|
invariant StructuredAuthErrors {
|
|
-- Auth failures produce structured guidance:
|
|
-- per platform (macOS/Windows/Linux)
|
|
-- per provider (GitHub/GitLab/Gitea)
|
|
-- Instead of raw git error messages
|
|
}
|
|
|
|
invariant BoundedGitExecution {
|
|
-- Local operations stop after config.local_timeout and network operations
|
|
-- stop after config.network_timeout. Timeout errors identify the operation
|
|
-- and preserve captured output; the spawned process is terminated.
|
|
}
|
|
|
|
invariant OfflineNetworkGate {
|
|
-- Fetch, pull, and push are refused by their UI/automation caller while
|
|
-- airplane mode is active. Repository inspection and local commits remain
|
|
-- available.
|
|
}
|
|
|
|
rule PruneLfsCache {
|
|
when: PruneLfsCacheRequested(project, retain_recent)
|
|
-- Prunes LFS cache with configurable recent commit retention
|
|
ensures: LfsCachePruned(project)
|
|
}
|