nobrainer-tech-flow 0.0.0-stage → 2.4.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/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +21 -0
- package/.codex-plugin/plugin.json +47 -0
- package/.opencode/INSTALL.md +37 -0
- package/.opencode/plugins/nobrainer-tech-skills.js +66 -0
- package/.pi/extensions/nobrainer-tech-skills.js +64 -0
- package/AGENTS.md +204 -0
- package/CLAUDE.md +204 -0
- package/CONTRIBUTING.md +70 -0
- package/GEMINI.md +1 -0
- package/LICENSE +21 -0
- package/README.md +658 -2
- package/SECURITY.md +33 -0
- package/adapters/bootstrap.md +27 -0
- package/assets/nobrainer-tech-logo.svg +1 -0
- package/assets/nobrainer-workflow.svg +155 -0
- package/assets/plugin-logo.png +0 -0
- package/bin/nobrainer-tech-flow.mjs +48 -0
- package/docs/BOUNDED_RUNNER.md +60 -0
- package/docs/CHAT_PLUGINS.md +29 -0
- package/docs/COMPATIBILITY.md +371 -0
- package/docs/INSTALL.md +626 -0
- package/docs/MIGRATION_TO_FLOW.md +117 -0
- package/docs/NAMING.md +39 -0
- package/docs/PRIVACY.md +9 -0
- package/docs/SESSION_RESTART.md +70 -0
- package/docs/SKILL_CURATION.md +124 -0
- package/docs/TESTING.md +232 -0
- package/docs/TRY_IT.md +109 -0
- package/docs/releases/v1.0.0.md +81 -0
- package/docs/releases/v1.1.0.md +85 -0
- package/docs/releases/v1.10.0.md +34 -0
- package/docs/releases/v1.11.0-publication-readback.md +25 -0
- package/docs/releases/v1.11.0.md +42 -0
- package/docs/releases/v1.12.0.md +30 -0
- package/docs/releases/v1.13.0.md +46 -0
- package/docs/releases/v1.14.0.md +43 -0
- package/docs/releases/v1.14.1.md +21 -0
- package/docs/releases/v1.2.0.md +87 -0
- package/docs/releases/v1.2.1.md +92 -0
- package/docs/releases/v1.3.0-publication-readback.md +69 -0
- package/docs/releases/v1.3.0.md +66 -0
- package/docs/releases/v1.3.1-publication-readback.md +76 -0
- package/docs/releases/v1.3.1.md +59 -0
- package/docs/releases/v1.4.0-publication-readback.md +81 -0
- package/docs/releases/v1.4.0.md +43 -0
- package/docs/releases/v1.5.0-publication-readback.md +76 -0
- package/docs/releases/v1.5.0.md +54 -0
- package/docs/releases/v1.6.0.md +64 -0
- package/docs/releases/v1.6.1-publication-readback.md +53 -0
- package/docs/releases/v1.6.1.md +82 -0
- package/docs/releases/v1.7.0.md +42 -0
- package/docs/releases/v1.7.1-publication-readback.md +64 -0
- package/docs/releases/v1.7.1.md +49 -0
- package/docs/releases/v1.8.0.md +63 -0
- package/docs/releases/v1.8.1.md +32 -0
- package/docs/releases/v1.9.0.md +33 -0
- package/docs/releases/v2.0.0-candidate.md +28 -0
- package/docs/releases/v2.0.0.md +53 -0
- package/docs/releases/v2.0.1.md +135 -0
- package/docs/releases/v2.0.2.md +189 -0
- package/docs/releases/v2.1.0.md +64 -0
- package/docs/releases/v2.1.1.md +43 -0
- package/docs/releases/v2.2.0.md +64 -0
- package/docs/releases/v2.2.1.md +11 -0
- package/docs/releases/v2.3.0.md +97 -0
- package/docs/releases/v2.3.1.md +71 -0
- package/docs/releases/v2.3.2.md +242 -0
- package/docs/releases/v2.4.0.md +126 -0
- package/gemini-extension.json +6 -0
- package/hooks/claude-hooks.json +17 -0
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/run-hook.cmd +36 -0
- package/hooks/session-start +38 -0
- package/package.json +69 -4
- package/plugin.json +51 -0
- package/scripts/build_plugin.py +50 -0
- package/scripts/check_flow_update.py +18 -0
- package/scripts/check_public_commit.py +86 -0
- package/scripts/fetch_public_refs.py +69 -0
- package/scripts/install.py +600 -0
- package/scripts/install.sh +1392 -0
- package/scripts/install_guards.py +279 -0
- package/scripts/install_personalization.py +527 -0
- package/scripts/install_skills.py +951 -0
- package/scripts/npm_install.py +85 -0
- package/scripts/recommend_flow_setup.py +1522 -0
- package/scripts/validate_skills.py +465 -0
- package/skills/nobrainer-auto-fine-tune/SKILL.md +79 -0
- package/skills/nobrainer-auto-fine-tune/references/capacity-audit.md +49 -0
- package/skills/nobrainer-autoimprove/SKILL.md +212 -0
- package/skills/nobrainer-autoimprove/scripts/count_words.py +22 -0
- package/skills/nobrainer-browser/SKILL.md +234 -0
- package/skills/nobrainer-browser/references/cdp-profile-restart.md +93 -0
- package/skills/nobrainer-build/SKILL.md +173 -0
- package/skills/nobrainer-codex-context/SKILL.md +111 -0
- package/skills/nobrainer-codex-context/scripts/codex_context.py +175 -0
- package/skills/nobrainer-decide/SKILL.md +198 -0
- package/skills/nobrainer-dispatcher/SKILL.md +256 -0
- package/skills/nobrainer-dispatcher/agents/openai.yaml +4 -0
- package/skills/nobrainer-rca/SKILL.md +163 -0
- package/skills/nobrainer-research/SKILL.md +131 -0
- package/skills/nobrainer-review/SKILL.md +195 -0
- package/skills/nobrainer-security/SKILL.md +166 -0
- package/skills/nobrainer-sessions/SKILL.md +223 -0
- package/skills/nobrainer-sessions/references/evidence-receipt.md +55 -0
- package/skills/nobrainer-sessions/references/protocol.md +337 -0
- package/skills/nobrainer-sessions/references/session-restart.md +255 -0
- package/skills/nobrainer-sessions/scripts/evidence_receipt.py +373 -0
- package/skills/nobrainer-sessions/scripts/restart_gate.py +140 -0
- package/skills/nobrainer-sessions/scripts/run_bounded.py +390 -0
- package/skills/nobrainer-sessions/scripts/session_title.py +43 -0
- package/skills/nobrainer-skill-doctor/SKILL.md +149 -0
- package/skills/nobrainer-skill-doctor/references/astra.md +19 -0
- package/skills/nobrainer-skill-doctor/scripts/audit.py +100 -0
- package/skills/nobrainer-skill-doctor/scripts/context_candidates.py +133 -0
- package/skills/nobrainer-spec-driven-development/SKILL.md +132 -0
- package/skills/nobrainer-spec-driven-development/references/spec-template.md +114 -0
- package/skills/nobrainer-team/SKILL.md +165 -0
- package/skills/nobrainer-team/references/team-plan.md +64 -0
- package/skills/nobrainer-tech-flow/SKILL.md +239 -0
- package/skills/nobrainer-tech-flow/VERSION +1 -0
- package/skills/nobrainer-tech-flow/references/autopilot.md +72 -0
- package/skills/nobrainer-tech-flow/references/codex-routing.md +21 -0
- package/skills/nobrainer-tech-flow/references/communication.md +108 -0
- package/skills/nobrainer-tech-flow/references/correction-hooks.md +110 -0
- package/skills/nobrainer-tech-flow/references/daily-update.md +28 -0
- package/skills/nobrainer-tech-flow/references/delivery.md +243 -0
- package/skills/nobrainer-tech-flow/references/idea-to-delivery.md +82 -0
- package/skills/nobrainer-tech-flow/references/long-run-state.md +209 -0
- package/skills/nobrainer-tech-flow/references/model-routing.md +83 -0
- package/skills/nobrainer-tech-flow/references/optional-decisions.md +83 -0
- package/skills/nobrainer-tech-flow/references/project-start.md +22 -0
- package/skills/nobrainer-tech-flow/references/routing.md +125 -0
- package/skills/nobrainer-tech-flow/references/setup.md +180 -0
- package/skills/nobrainer-tech-flow/references/worktrees.md +138 -0
- package/skills/nobrainer-tech-flow/scripts/check_flow_update.py +298 -0
- package/skills/nobrainer-tech-flow/scripts/cleanup_worktree.py +327 -0
- package/skills/nobrainer-tech-flow/scripts/decision_config.py +107 -0
- package/skills/nobrainer-tech-flow/scripts/flow_guard.py +252 -0
- package/skills/nobrainer-tech-flow/scripts/typed_decisions.py +210 -0
- package/skills/nobrainer-wiki/SKILL.md +164 -0
- package/skills/nobrainer-wiki/references/setup.md +88 -0
- package/skills/nobrainer-writing/SKILL.md +272 -0
- package/skills/nobrainer-writing/agents/openai.yaml +4 -0
- package/skills/nobrainer-writing/references/brief-artifacts.md +357 -0
- package/skills/nobrainer-writing/references/plain-technical-english.md +76 -0
- package/skills/nobrainer-writing/references/research.md +86 -0
package/plugin.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "nobrainer-tech-flow",
|
|
4
|
+
"description": "From task to done. nobrainer-tech-flow is one workflow of 18 modules for AI coding agents: it scopes the task, delegates ready work, checks the result and hands over the evidence, including what was not checked.",
|
|
5
|
+
"version": "2.4.0",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "NoBrainer Tech",
|
|
8
|
+
"url": "https://nobrainer.tech"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://nobrainer.tech/flow/",
|
|
11
|
+
"repository": "https://github.com/nobrainer-tech/nobrainer-tech-flow",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"workflow",
|
|
15
|
+
"agent-skills",
|
|
16
|
+
"research",
|
|
17
|
+
"writing",
|
|
18
|
+
"planning",
|
|
19
|
+
"verification"
|
|
20
|
+
],
|
|
21
|
+
"extensions": {
|
|
22
|
+
"com.openai": {
|
|
23
|
+
"interface": {
|
|
24
|
+
"displayName": "nobrainer-tech-flow",
|
|
25
|
+
"shortDescription": "From task to done.",
|
|
26
|
+
"longDescription": "nobrainer-tech-flow is one workflow of 18 modules for coding, research, writing and planning. It clarifies the outcome, delegates independent ready work to native workers, shares concise progress, checks the result and hands over what was delivered, the evidence and what was not checked. It uses the tools and permissions your client already grants.",
|
|
27
|
+
"developerName": "NoBrainer Tech",
|
|
28
|
+
"category": "Developer Tools",
|
|
29
|
+
"capabilities": [
|
|
30
|
+
"Interactive",
|
|
31
|
+
"Read",
|
|
32
|
+
"Write"
|
|
33
|
+
],
|
|
34
|
+
"defaultPrompt": [
|
|
35
|
+
"Use `nobrainer-tech-flow` to fix this bug: reproduce it, make the smallest fix, run the check and say what is unverified.",
|
|
36
|
+
"Use `nobrainer-tech-flow` to turn my idea into a plan with acceptance checks, then deliver and verify it.",
|
|
37
|
+
"Use `nobrainer-tech-flow` to compare these options with sources and give one recommendation."
|
|
38
|
+
],
|
|
39
|
+
"websiteURL": "https://nobrainer.tech/flow/",
|
|
40
|
+
"privacyPolicyURL": "https://github.com/nobrainer-tech/nobrainer-tech-flow/blob/main/docs/PRIVACY.md",
|
|
41
|
+
"termsOfServiceURL": "https://github.com/nobrainer-tech/nobrainer-tech-flow/blob/main/LICENSE",
|
|
42
|
+
"brandColor": "#2948B8",
|
|
43
|
+
"composerIcon": "./assets/plugin-logo.png",
|
|
44
|
+
"logo": "./assets/plugin-logo.png",
|
|
45
|
+
"screenshots": [],
|
|
46
|
+
"supportURL": "https://github.com/nobrainer-tech/nobrainer-tech-flow/issues"
|
|
47
|
+
},
|
|
48
|
+
"onboardingSkill": "./skills/nobrainer-tech-flow/SKILL.md"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Build a hook-free portable plugin ZIP from the same eighteen canonical skills."""
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import zipfile
|
|
7
|
+
|
|
8
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
9
|
+
DIRECTORIES = ("skills", "scripts", "adapters", "docs", "assets")
|
|
10
|
+
ROOT_FILES = ("plugin.json", "README.md", "LICENSE", "SECURITY.md", "package.json")
|
|
11
|
+
EXCLUDED = {"__pycache__", ".DS_Store", "node_modules"}
|
|
12
|
+
ARCHIVES = {".zip", ".tgz", ".tar", ".gz"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build(output: Path) -> None:
|
|
16
|
+
version = json.loads((ROOT / "plugin.json").read_text())["version"]
|
|
17
|
+
files = []
|
|
18
|
+
for directory in DIRECTORIES:
|
|
19
|
+
for path in sorted((ROOT / directory).rglob("*")):
|
|
20
|
+
if path.is_symlink():
|
|
21
|
+
raise ValueError("plugin source must not contain symlinks: " + str(path))
|
|
22
|
+
if path.is_file() and not set(path.parts) & EXCLUDED and path.suffix not in ARCHIVES | {".pyc"}:
|
|
23
|
+
if directory == "docs" and path.parent not in (ROOT / "docs", ROOT / "docs/releases"):
|
|
24
|
+
continue
|
|
25
|
+
if directory == "assets" and path.suffix not in (".svg", ".png"):
|
|
26
|
+
continue
|
|
27
|
+
files.append(path)
|
|
28
|
+
files.extend(ROOT / name for name in ROOT_FILES)
|
|
29
|
+
skills = list((ROOT / "skills").glob("*/SKILL.md"))
|
|
30
|
+
if len(skills) != 18:
|
|
31
|
+
raise ValueError("plugin requires the exact eighteen-module portfolio")
|
|
32
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
if output.exists() or output.is_symlink():
|
|
34
|
+
raise ValueError("output already exists; choose a fresh path")
|
|
35
|
+
with zipfile.ZipFile(output, "x", compression=zipfile.ZIP_DEFLATED) as archive:
|
|
36
|
+
for path in files:
|
|
37
|
+
archive.write(path, path.relative_to(ROOT).as_posix())
|
|
38
|
+
overlay = json.loads((ROOT / ".codex-plugin/plugin.json").read_text())
|
|
39
|
+
archive.writestr(".codex-plugin/plugin.json", json.dumps(overlay, indent=2) + "\n")
|
|
40
|
+
claude = json.loads((ROOT / ".claude-plugin/plugin.json").read_text())
|
|
41
|
+
claude.pop("hooks", None)
|
|
42
|
+
archive.writestr(".claude-plugin/plugin.json", json.dumps(claude, indent=2) + "\n")
|
|
43
|
+
print(f"PLUGIN_READY: {output.name}; version={version}; skills=18; hooks=none; bytes={output.stat().st_size}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
48
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
49
|
+
args = parser.parse_args()
|
|
50
|
+
build(args.output)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Repository wrapper for the installed skill's nobrainer-tech-flow update checker."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_HELPER_DIR = Path(__file__).resolve().parents[1] / "skills" / "nobrainer-tech-flow" / "scripts"
|
|
11
|
+
sys.path.insert(0, str(_HELPER_DIR))
|
|
12
|
+
|
|
13
|
+
# Keep the historical import surface available to tests and repository callers.
|
|
14
|
+
from check_flow_update import * # noqa: E402,F401,F403
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fail closed before a public-repository commit uses automation or backup state."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
AUTOMATED_IDENTITY_RE = re.compile(
|
|
12
|
+
r"\b(?:auto|automated|automation|backup|bot)\b",
|
|
13
|
+
re.IGNORECASE,
|
|
14
|
+
)
|
|
15
|
+
BACKUP_PATH_RE = re.compile(
|
|
16
|
+
r"(?:^|/)(?:\.git-backups|backup|backups)(?:/|$)"
|
|
17
|
+
r"|(?:^|/)[^/]*\.(?:bak|backup)(?:[-.]|$)",
|
|
18
|
+
re.IGNORECASE,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _git(*args: str) -> subprocess.CompletedProcess[str]:
|
|
23
|
+
try:
|
|
24
|
+
return subprocess.run(
|
|
25
|
+
["git", *args],
|
|
26
|
+
# Git emits UTF-8 names and paths whatever the console code page is.
|
|
27
|
+
encoding="utf-8",
|
|
28
|
+
errors="replace",
|
|
29
|
+
capture_output=True,
|
|
30
|
+
check=False,
|
|
31
|
+
)
|
|
32
|
+
except OSError as exc:
|
|
33
|
+
raise RuntimeError(f"git is unavailable: {exc}") from None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _identity(variable: str) -> tuple[str, str]:
|
|
37
|
+
result = _git("var", variable)
|
|
38
|
+
if result.returncode != 0:
|
|
39
|
+
detail = result.stderr.strip() or "git var failed"
|
|
40
|
+
raise RuntimeError(f"{variable}: {detail}")
|
|
41
|
+
match = re.match(r"^(?P<name>.+) <(?P<email>[^>]+)>", result.stdout.strip())
|
|
42
|
+
if match is None:
|
|
43
|
+
raise RuntimeError(f"{variable}: unparseable identity")
|
|
44
|
+
return match.group("name"), match.group("email")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _staged_paths() -> tuple[str, ...]:
|
|
48
|
+
result = _git("diff", "--cached", "--name-only", "-z")
|
|
49
|
+
if result.returncode != 0:
|
|
50
|
+
detail = result.stderr.strip() or "git diff failed"
|
|
51
|
+
raise RuntimeError(f"staged paths: {detail}")
|
|
52
|
+
return tuple(path for path in result.stdout.split("\0") if path)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main() -> int:
|
|
56
|
+
violations: list[str] = []
|
|
57
|
+
try:
|
|
58
|
+
for variable in ("GIT_AUTHOR_IDENT", "GIT_COMMITTER_IDENT"):
|
|
59
|
+
name, email = _identity(variable)
|
|
60
|
+
if AUTOMATED_IDENTITY_RE.search(f"{name} <{email}>"):
|
|
61
|
+
violations.append(f"{variable}={name} <{email}>")
|
|
62
|
+
|
|
63
|
+
for path in _staged_paths():
|
|
64
|
+
if BACKUP_PATH_RE.search(path):
|
|
65
|
+
violations.append(f"staged backup path={path}")
|
|
66
|
+
except RuntimeError as exc:
|
|
67
|
+
print(f"PUBLIC_COMMIT_GUARD: ERROR: {exc}", file=sys.stderr)
|
|
68
|
+
return 2
|
|
69
|
+
|
|
70
|
+
if violations:
|
|
71
|
+
print("PUBLIC_COMMIT_GUARD: BLOCKED", file=sys.stderr)
|
|
72
|
+
for violation in violations:
|
|
73
|
+
print(f"- {violation}", file=sys.stderr)
|
|
74
|
+
print(
|
|
75
|
+
"Use a human repository identity and keep backup state in the "
|
|
76
|
+
"private repository.",
|
|
77
|
+
file=sys.stderr,
|
|
78
|
+
)
|
|
79
|
+
return 1
|
|
80
|
+
|
|
81
|
+
print("PUBLIC_COMMIT_GUARD: PASS")
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fetch public remote refs without changing the working tree."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import re
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
REMOTE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _git(*args: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
|
|
17
|
+
return subprocess.run(
|
|
18
|
+
["git", *args],
|
|
19
|
+
cwd=cwd,
|
|
20
|
+
text=True,
|
|
21
|
+
capture_output=True,
|
|
22
|
+
check=False,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parser() -> argparse.ArgumentParser:
|
|
27
|
+
parser = argparse.ArgumentParser(
|
|
28
|
+
description="Update one public remote-tracking ref; leave the worktree untouched."
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument("--remote", default="origin")
|
|
31
|
+
parser.add_argument("--branch", default="main")
|
|
32
|
+
return parser
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main(argv: list[str] | None = None) -> int:
|
|
36
|
+
args = _parser().parse_args(argv)
|
|
37
|
+
if REMOTE_RE.fullmatch(args.remote) is None or args.remote.startswith("-"):
|
|
38
|
+
print("PUBLIC_FETCH: ERROR: invalid remote name", file=sys.stderr)
|
|
39
|
+
return 2
|
|
40
|
+
if args.branch.startswith("-"):
|
|
41
|
+
print("PUBLIC_FETCH: ERROR: invalid branch name", file=sys.stderr)
|
|
42
|
+
return 2
|
|
43
|
+
|
|
44
|
+
root_result = _git("rev-parse", "--show-toplevel")
|
|
45
|
+
if root_result.returncode != 0:
|
|
46
|
+
print("PUBLIC_FETCH: ERROR: not a Git worktree", file=sys.stderr)
|
|
47
|
+
return 2
|
|
48
|
+
root = Path(root_result.stdout.strip())
|
|
49
|
+
|
|
50
|
+
branch_result = _git("check-ref-format", "--branch", args.branch, cwd=root)
|
|
51
|
+
if branch_result.returncode != 0:
|
|
52
|
+
print("PUBLIC_FETCH: ERROR: invalid branch name", file=sys.stderr)
|
|
53
|
+
return 2
|
|
54
|
+
|
|
55
|
+
refspec = f"refs/heads/{args.branch}:refs/remotes/{args.remote}/{args.branch}"
|
|
56
|
+
fetch_result = _git("fetch", "--no-tags", args.remote, refspec, cwd=root)
|
|
57
|
+
if fetch_result.returncode != 0:
|
|
58
|
+
print(
|
|
59
|
+
f"PUBLIC_FETCH: ERROR: fetch failed (exit {fetch_result.returncode})",
|
|
60
|
+
file=sys.stderr,
|
|
61
|
+
)
|
|
62
|
+
return fetch_result.returncode or 1
|
|
63
|
+
|
|
64
|
+
print(f"PUBLIC_FETCH: PASS remote={args.remote} branch={args.branch}")
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
raise SystemExit(main())
|