Skip to content

Monorepo for nightops#19

Open
SuchitraSwain wants to merge 4 commits into
nomadicmehul:mainfrom
SuchitraSwain:mono-repo
Open

Monorepo for nightops#19
SuchitraSwain wants to merge 4 commits into
nomadicmehul:mainfrom
SuchitraSwain:mono-repo

Conversation

@SuchitraSwain
Copy link
Copy Markdown

No description provided.

@nomadicmehul nomadicmehul requested a review from Copilot March 26, 2026 14:20
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds monorepo workspace scaffolding and improves “out-of-the-box” behavior by loading default config/policy YAML from packaged resources when no filesystem config exists.

Changes:

  • Load packaged default remediation policies and default nightops.yaml via importlib.resources.
  • Include the config/ directory in the built wheel and adjust pyproject.toml license metadata.
  • Add a monorepo/ workspace layout with docs and a packages/nightops pointer.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/remediation/policy_engine.py Fallback to packaged remediation policy YAML when no local policy file exists.
src/core/config.py Support loading packaged default config YAML and add YAML-text loader with env substitution.
pyproject.toml Include config/ in wheel build and update license metadata format.
monorepo/packages/nightops Adds a “pointer” to repo root for monorepo workspace usage.
monorepo/README.md Documents monorepo setup, CLI usage, and publishing steps.
Comments suppressed due to low confidence (1)

monorepo/packages/nightops:1

  • monorepo/README.md describes monorepo/packages/nightops as a symlink, but this appears to be a regular file containing ../../. That won’t behave like an installable project directory for pip install -e "packages/nightops[dev]". Either commit an actual symlink (Git supports this) or switch to a documented approach that doesn’t require symlinks (e.g., a path dependency / editable install pointing at ../..) and update the README accordingly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/core/config.py Outdated
Comment thread src/remediation/policy_engine.py Outdated
Comment thread src/core/config.py Outdated
Comment thread src/core/config.py Outdated
Comment on lines +369 to +382
def from_yaml_text(cls, raw: str) -> NightOpsConfig:
"""Load configuration from YAML text with environment variable substitution.

This is used for packaged-in defaults where there is no filesystem path
alongside the YAML file (so we can't load an adjacent `config/.env`).
"""
# Substitute environment variables (${VAR_NAME} syntax)
for key, value in os.environ.items():
raw = raw.replace(f"${{{key}}}", value)

# Replace any remaining unresolved ${VAR} references with empty string
raw = re.sub(r"\$\{[A-Za-z_][A-Za-z0-9_]*\}", "", raw)

data = yaml.safe_load(raw)
data = yaml.safe_load(raw) or {}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Environment substitution + YAML parsing + mcp_servers normalization now exists in both from_yaml and from_yaml_text, which increases the chance they drift (the or {} fix already differs between the two). Consider extracting a shared internal helper (e.g., _parse_yaml_config(raw: str) -> dict) that both entry points call, keeping file-specific behavior (like reading .env relative to a path) separate.

Copilot uses AI. Check for mistakes.
Comment thread pyproject.toml
[tool.hatch.build.targets.wheel.force-include]
"src" = "nightops"
"config" = "nightops/config"

Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runtime code now depends on packaged resources (nightops/config/nightops.yaml). This change force-includes config/ for wheels, but it may still be missing from sdists depending on hatch configuration, which can break installs that build from sdist. Consider also including config/ in the sdist target (or otherwise ensuring all package builds include these YAML resources).

Suggested change
[tool.hatch.build.targets.sdist]
include = [
"src",
"config",
]

Copilot uses AI. Check for mistakes.
Comment thread monorepo/README.md Outdated
@@ -0,0 +1,137 @@
# Monorepo Layout (Local Workspace)

This repo is already packaged as a Python distribution named `nightops` (see `/Users/suchitraswain/Documents/google/TheNightOps/pyproject.toml`).
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README hard-codes an absolute local filesystem path, which won’t apply to other developers/CI and makes the instructions non-portable. Replace with repo-relative paths (e.g., “from the repository root”) and, if needed, show examples using $REPO_ROOT or pwd-based paths.

Copilot uses AI. Check for mistakes.
Comment thread monorepo/README.md Outdated
@nomadicmehul
Copy link
Copy Markdown
Owner

@SuchitraSwain review this

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@nomadicmehul nomadicmehul linked an issue Mar 26, 2026 that may be closed by this pull request
@nomadicmehul nomadicmehul requested a review from Copilot March 26, 2026 20:51
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

monorepo/packages/nightops:1

  • This appears to be a Git symlink (stored as a file containing the link target). Symlinks can be problematic on Windows and in some packaging/checkout workflows (zip downloads, restricted filesystem permissions), which may break the documented pip install -e \"packages/nightops[dev]\" flow. If cross-platform support is needed, consider an alternative (e.g., a small packages/nightops/pyproject.toml that depends on the root package, or workspace tooling that doesn’t require symlinks), or explicitly document the symlink requirement/limitations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/core/config.py
Comment on lines +47 to +49
for key, value in os.environ.items():
raw = raw.replace(f"${{{key}}}", value)
raw = re.sub(r"\$\{[A-Za-z_][A-Za-z0-9_]*\}", "", raw)
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two-pass env substitution can corrupt legitimate values that contain ${...} after replacement (e.g., if an env var value itself contains ${HOME}, the subsequent regex will delete it). A safer approach is a single re.sub pass with a callback that replaces only ${NAME} tokens by looking up NAME in os.environ (defaulting to empty string when missing), and avoids scanning/replacing for every env var.

Suggested change
for key, value in os.environ.items():
raw = raw.replace(f"${{{key}}}", value)
raw = re.sub(r"\$\{[A-Za-z_][A-Za-z0-9_]*\}", "", raw)
pattern = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
raw = pattern.sub(lambda m: os.environ.get(m.group(1), ""), raw)

Copilot uses AI. Check for mistakes.
@@ -140,6 +142,47 @@ def _load_policies(self, path: str) -> None:
"""Load policies from a YAML file, merging with defaults."""
policy_file = Path(path)
if not policy_file.exists():
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The packaged-default lookup reuses the user-supplied path string verbatim. If callers pass an absolute filesystem path (or a path with drive letters / path separators), the join against package resources becomes ambiguous and can mask a simple 'file not found' misconfiguration. Consider only attempting packaged lookup for expected relative default locations (e.g., when not policy_file.is_absolute()), or normalize to a known resource-relative path before joining.

Suggested change
if not policy_file.exists():
if not policy_file.exists():
# Only attempt packaged lookup for non-absolute paths to avoid
# ambiguities when users pass absolute filesystem locations.
if policy_file.is_absolute():
logger.info("No policy file at %s, using defaults", path)
return

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Monorepo for python

3 participants